如何在可组合函数回调中调用 Kotlin 协程?
Posted
技术标签:
【中文标题】如何在可组合函数回调中调用 Kotlin 协程?【英文标题】:How to call Kotlin coroutine in composable function callbacks? 【发布时间】:2021-01-14 20:58:51 【问题描述】:我想在可组合函数的回调中调用挂起函数。
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
是@Composable
而getLocationOnClick
不能是@Composable
。
在 @Composable
函数内的常规函数内获取挂起函数结果的最佳方法是什么?
【问题讨论】:
您可以调用 ViewModel 的函数,该函数在 viewmodelScope 上启动挂起函数 否则,如果您有对 AppCompatActivity 的引用,则可以使用其生命周期范围 你的意思是像SomeScope.async setLocation(getLocation())
?谢谢,这确实有效,我没想到它会那么简单。请您将其评论为答案(以便我可以将此问题标记为已解决)。
【参考方案1】:
创建一个与可组合的生命周期相关联的协程范围,并使用该范围来调用您的挂起函数
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")
【讨论】:
【参考方案2】:您可以使用 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
)
【讨论】:
在viewModelScope extension source code 上抢先一步。可以在没有 ViewModel 的情况下使用val scope = remember CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
然后 onActive onDispose scope.cancel()
吗?
不太好,你真的不知道操作什么时候结束。只需使用 rememberCoroutinesScope() 并将 deleteItem() 变成一个挂起函数【参考方案3】:
这对我有用:
@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")
【讨论】:
以上是关于如何在可组合函数回调中调用 Kotlin 协程?的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin 协程协程启动 ③ ( 协程组合并发 | 挂起函数串行执行 | 协程组合并发执行挂起函数 )
Kotlin 协程协程启动 ③ ( 协程组合并发 | 挂起函数串行执行 | 协程组合并发执行挂起函数 )
Kotlin 协程Flow 流组合 ( Flow#zip 组合多个流 | 新组合流的元素收集间隔与被组合流元素发射间隔的联系 )
Kotlin 协程Flow 流组合 ( Flow#zip 组合多个流 | 新组合流的元素收集间隔与被组合流元素发射间隔的联系 )