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

Nyc*_*cta 8 android kotlin kotlin-coroutines android-jetpack-compose

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

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")
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

我不能使用launchInCompositioningetLocationOnClick因为launchInCompositionis@ComposablegetLocationOnClickcan not be @Composable

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

hey*_*hey 18

创建一个与可组合的生命周期相关联的协同作用域,并使用该作用域调用您的挂起函数

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")
    }
}
Run Code Online (Sandbox Code Playgroud)


Dir*_*ann 11

这对我有用:

@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")
    }
Run Code Online (Sandbox Code Playgroud)

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

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()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@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")
    }

}

Run Code Online (Sandbox Code Playgroud)


2ja*_*222 5

您可以使用 ViewModel 的 viewModelScope 或任何其他协程作用域。

从 LazyColumnFor 删除项目操作的示例,需要由 ViewModel 处理的挂起调用。

     class ItemsViewModel : ViewModel() {

        private val _itemList = MutableLiveData<List<Any>>()
        val itemList: LiveData<List<Any>>
            get() = _itemList

        fun deleteItem(item: Any) {
            viewModelScope.launch(Dispatchers.IO) {
                TODO("Fill Coroutine Scope with your suspend call")       
            }
        }
    }

    @Composable
    fun Example() {
        val itemsVM: ItemsViewModel = viewModel()
        val list: State<List<Any>?> = itemsVM.itemList.observeAsState()
        list.value.let { it: List<Any>? ->
            if (it != null) {
                LazyColumnFor(items = it) { item: Any ->
                    ListItem(
                        item = item,
                        onDeleteSelf = {
                            itemsVM.deleteItem(item)
                        }
                    )
                }
            } // else EmptyDialog()
        }
    }

    @Composable
    private fun ListItem(item: Any, onDeleteSelf: () -> Unit) {
        Row {
            Text(item.toString())
            IconButton(
                onClick = onDeleteSelf,
                icon = { Icons.Filled.Delete }
            )
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 抢先浏览了 [viewModelScope 扩展源代码](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/lifecycle/lifecycle-viewmodel-ktx/src /main/java/androidx/lifecycle/ViewModel.kt)。是否可以在没有 ViewModel 的情况下仅使用“valscope = Remember { CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) }”然后使用“onActive { onDispose {scope.cancel() } }”? (2认同)