Mehdi Haghgoo
02/17/2021, 5:08 PM@Composable
fun Detail(id: Int, viewModel: CostViewModel){
var cost by mutableStateOf(Cost(costName = "sample", costValue = 10.0))
GlobalScope.launch{cost = viewModel.findCost(id)}
Surface(modifier = Modifier.shadow(4.dp)) {
Column {
BasicText(stringResource(id = R.string.prodcuct_details))
BasicText("Cost: ${cost.costName}")
BasicText("Value: ${cost.costValue}")
Image(
painterResource(id = getRandomImageResource()),
contentDescription = cost.costName
)
}
}
}
jaqxues
02/17/2021, 5:11 PMremember { mutableStateOf() }
Mehdi Haghgoo
02/17/2021, 5:12 PMFATAL EXCEPTION: DefaultDispatcher-worker-1
Process: <http://com.example.app|com.example.app>, PID: 19134
java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
at androidx.compose.runtime.snapshots.SnapshotKt.readError(Snapshot.kt:1688)
at androidx.compose.runtime.snapshots.SnapshotKt.current(Snapshot.kt:1864)
at androidx.compose.runtime.SnapshotMutableStateImpl.setValue(MutableState.kt:324)
at com.example.app.MainActivityKt.Detail$lambda-19(MainActivity.kt:550)
at com.example.app.MainActivityKt.access$Detail$lambda-19(MainActivity.kt:1)
at com.example.app.MainActivityKt$Detail$1.invokeSuspend(MainActivity.kt:324)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
jaqxues
02/17/2021, 5:14 PMMehdi Haghgoo
02/17/2021, 5:14 PMfindCost()
return LiveData?jaqxues
02/17/2021, 5:17 PMfindCost
just returns a Cost object (which is fine). Although I would not use GlobalScope, but a rememberCoroutineScope()
or maybe even sth like a LaunchedEffect()
Besides that, I do not really see immediately what else is wrong with your codeGlobalScope.launch {
val tmp = viewModel.findCost(id)
withContext(Dispatchers.Main) {
cost = tmp
}
}
Maybe this will help, but again, not sureMehdi Haghgoo
02/17/2021, 5:22 PMjaqxues
02/17/2021, 5:26 PMComposable
function as sth that will be executed over and over again (not under your control).
When not using remember, you create a new mutableState every single time the composable is refreshed.
Similarly, GlobalScope.launch
is terrible since it will launch new coroutines every time it refreshes. You should not assume anything about recomposition. Make your code safe to be executed often, and let compose handle the rest.
I would use sth like
LaunchedEffect(id) {
// your coroutine code
}
Mehdi Haghgoo
02/17/2021, 5:31 PMAdam Powell
02/17/2021, 5:32 PMCoroutineScope.launch
from the body of a composable function. (Note that this is distinct from using launch
in an event handler created in a composable function.)jaqxues
02/17/2021, 5:32 PMAdam Powell
02/17/2021, 5:33 PMmutableStateOf
in composition and then pass it to another thread, as the GlobalScope.launch
in the problem snippet does, you've essentially let a reference to snapshot state that doesn't exist yet escape from the transaction.LaunchedEffect
doesn't experience any issues around any of this is because LaunchedEffect
doesn't actually launch a coroutine until after the composition has successfully completed and applied - after the composition snapshot commits successfully.val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doSuspendingThings() } }) {
because the launch is part of the click handler, not part of the composable function.Mehdi Haghgoo
02/17/2021, 5:59 PMAdam Powell
02/17/2021, 7:26 PMvar cost by remember { mutableStateOf(Cost(costName = "sample", costValue = 10.0)) }
LaunchedEffect(id) {
cost = viewModel.findCost(id)
}
LaunchedEffect
runs on the composition's effect context, i.e. the UI thread. You can use withContext
to switch dispatchers as usual.Mehdi Haghgoo
02/17/2021, 9:11 PMval cost by viewModel.findCost(id).collectAsState(Cost(costName = "default", costValue = 1.0))
Surface(modifier = Modifier.shadow(4.dp)) {
Column {
BasicText(stringResource(id = R.string.prodcuct_details))
BasicText("Cost: ${cost.costName}")
BasicText("Value: ${cost.costValue}")
Image(
painterResource(id = getRandomImageResource()),
contentDescription = cost.costName
)
}
}
Adam Powell
02/17/2021, 10:24 PMfindCost(id)
doesn't return the same flow object instance every time for the same id you'll likely want to spell that as
val cost by remember(viewModel, id) {
viewModel.findCost(id)
}.collectAsState(...)
collectAsState
will cancel the old collect and start a new one every time that recomposes