繁体   English   中英

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

[英]How to call Kotlin coroutine in composable function callbacks?

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

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是实验性的。

我不能在getLocationOnClick使用launchInComposition因为launchInComposition@ComposablegetLocationOnClick不能是@Composable

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

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

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

您可以使用 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 }
            )
        }
    }

这对我有用:

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM