Nycta
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
.
Yo podría hacer 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 @Composable
¿función?
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")
}
}
-
desarrollador.android.com/jetpack/compose/kotlin#coroutines … de los documentos
–Tom Howard
31 de mayo a las 0:27
Dirk Hoffman
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 }
)
}
}
-
echó un vistazo a la código fuente de la extensión viewModelScope. ¿Podría usarse sin ViewModel solo con
val scope = remember { CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) }
y luegoonActive { onDispose { scope.cancel() } }
?– Nycta
1 de octubre de 2020 a las 5:33
-
No muy bien, realmente no sabes cuándo termina la operación. Simplemente use RememberCoroutinesScope() y convierta deleteItem() en una función de suspensión
– Hey hey hey
14 de noviembre de 2020 a las 16:18
-
Estoy de acuerdo con @heyheyhey, no sabemos cuándo se llamará al componible, tal vez se recuerde varias veces, por lo que es mejor usarlo
rememberCoroutineScope
encimaviewModelScope
– gtxtremo
27 de mayo de 2022 a las 4:34
Puede llamar a una función de ViewModel que inicia una función de suspensión en viewmodelScope
– 2ene222
29 de septiembre de 2020 a las 9:26
De lo contrario, si tiene una referencia a AppCompatActivity, podría usar su ámbito de ciclo de vida.
– 2ene222
29 de septiembre de 2020 a las 9:27
Te refieres a
SomeScope.async { setLocation(getLocation()) }
? Gracias, eso realmente funciona, no esperaba que fuera tan simple. ¿Podría comentar eso como respuesta (para que pueda marcar esta pregunta como resuelta)?– Nycta
30 de septiembre de 2020 a las 7:27