如何在可组合函数回调中调用 Kotlin 协程?

新手上路,请多包涵

我想在可组合函数的回调中调用一个挂起函数。

 suspend fun getLocation(): Location? { /* ... */ }

@Composable
fun F() {

    val (location, setLocation) = remember { mutableStateOf<Location?>(null) }

    val getLocationOnClick: () -> Unit = {
        /* setLocation __MAGIC__ getLocation */
    }

    Button(onClick = getLocationOnClick) {
        Text("detectLocation")
    }

}

如果我会使用 Rx,那么我可以 subscribe

我可以做 invokeOnCompletion 然后 getCompleted ,但该 API 是实验性的。

I can’t use launchInComposition in getLocationOnClick because launchInComposition is @Composable and getLocationOnClick can not be @Composable

@Composable 函数内部的常规函数中获得挂起函数结果的最佳方法是什么?

原文由 Nycta 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 664
2 个回答

创建一个协程作用域,绑定到可组合项的生命周期,并使用该作用域调用您的挂起函数

suspend fun getLocation(): Location? { /* ... */ }

@Composable
fun F() {
    // Returns a scope that's cancelled when F is removed from composition
    val coroutineScope = rememberCoroutineScope()

    val (location, setLocation) = remember { mutableStateOf<Location?>(null) }

    val getLocationOnClick: () -> Unit = {
        coroutineScope.launch {
            val location = getLocation()
        }
    }

    Button(onClick = getLocationOnClick) {
        Text("detectLocation")
    }
}

原文由 heyheyhey 发布,翻译遵循 CC BY-SA 4.0 许可协议

这对我有用:

 @Composable
fun TheComposable() {

    val coroutineScope = rememberCoroutineScope()
    val (loadResult, setLoadResult) = remember { mutableStateOf<String?>(null) }

    IconButton(
        onClick = {
            someState.startProgress("Draft Loading...")
            coroutineScope.launch {
                withContext(Dispatchers.IO) {
                    try {
                        loadResult = DataAPI.getData() // <-- non-suspend blocking method
                    } catch (e: Exception) {
                        // handle exception
                    } finally {
                        someState.endProgress()
                    }
                }
            }

        }
    ) {
        Icon(Icons.TwoTone.Call, contentDescription = "Load")
    }

我还尝试了以下辅助函数,以强制开发同事处理异常并最终清理状态(也使相同的代码(也许!?)更短一点并且(也许!?)更具可读性):

 fun launchHelper(coroutineScope: CoroutineScope,
                 catchBlock: (Exception) -> Unit,
                 finallyBlock: () -> Unit,
                 context: CoroutineContext = EmptyCoroutineContext,
                 start: CoroutineStart = CoroutineStart.DEFAULT,
                 block: suspend CoroutineScope.() -> Unit
): Job {
    return coroutineScope.launch(context, start) {
        withContext(Dispatchers.IO) {
            try {
                block()
            } catch (e: Exception) {
                catchBlock(e)
            } finally {
                finallyBlock()
            }
        }
    }
}

以下是如何使用该辅助方法:

 @Composable
fun TheComposable() {

    val coroutineScope = rememberCoroutineScope()
    val (loadResult, setLoadResult) = remember { mutableStateOf<String?>(null) }

    IconButton(
        onClick = {
            someState.startProgress("Draft Loading...")
            launchHelper(coroutineScope,
                catchBlock = { e -> myExceptionHandling(e) },
                finallyBlock = { someState.endProgress() }
            ) {
                loadResult = DataAPI.getData() // <-- non-suspend blocking method
            }

        }
    ) {
        Icon(Icons.TwoTone.Call, contentDescription = "Load")
    }

}

原文由 Dirk Hoffmann 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题