Quiero llamar a una función de suspensión dentro de una devolución de llamada de función componible.
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") } } Si hubiera usado Rx, entonces podría simplemente subscribe .
Podría invokeOnCompletion y luego getCompleted , pero esa API es experimental.
No puedo usar launchInComposition en getLocationOnClick porque launchInComposition es @Composable y getLocationOnClick no puede ser @Composable .
¿Cuál sería la mejor manera de obtener el resultado de una función de suspensión dentro de una función normal, dentro de la función @Composable ?
Esto funciona para mí:
@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") }También probé la siguiente función de ayuda, para forzar a los colegas desarrolladores a manejar Excepciones y finalmente limpiar el estado (también para hacer el mismo código (¡quizás!?) un poco más corto y (¡quizás!?) un poco más legible):
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() } } } }y he aquí cómo usar ese método auxiliar:
@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") } }Puede usar viewModelScope de un ViewModel o cualquier otro ámbito de rutina.
Ejemplo de acción de eliminación para un elemento de LazyColumnFor que requiere una llamada de suspensión manejada por un modelo de vista.
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 } ) } }Cree un alcance de rutinas, vinculado al ciclo de vida de su componible, y use ese alcance para llamar a su función de suspensión
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") } }