Bradleycorn
12/04/2020, 7:16 PMremember()
but that launches its calculation
in a coroutine scoped to the composable? Something like:
val myVal by remember(viewModel) {
viewModel.someSuspendingMethod()
}
My use case is that I have a suspend
method in a ViewModel who’s return value I need to remember
. Sure I could make the ViewModel method a “normal” method and use viewModelScope
to launch a coroutine, but that is going to be scoped to the ViewModel, which itself is scoped to the Activity. I want it to be scoped to the composable (so that if the composable is removed from the tree, the coroutine is canceled).
I guess I could use produceState
for this? Is that the best way?
val myVal by produceState(initialValue = "Loading...", subject = viewModel) {
value = viewModel.someSuspendingMethod()
}
Zach Klippenstein (he/him) [MOD]
12/04/2020, 7:16 PMLaunchedEffect
Bradleycorn
12/04/2020, 7:18 PMLaunchedEffect
?Zach Klippenstein (he/him) [MOD]
12/04/2020, 7:24 PMproduceState
like you said, i should have read your whole message 🙈produceState
does under the hood is combine LaunchedEffect
with a MutableState
basicallyBradleycorn
12/04/2020, 7:26 PMLaunchedEffect
, and another non-suspending method with remember
to get data from the database with a Flow.
I actually had it like that at first …
LaunchedEffect(viewModel) {
viewModel.updateData()
}
val myVal by remember(viewModel) {
viewModel.getDbData()
}
produceState
solution seems more concise and readable to me, and like you said, it’s essentially doing the same thing under the hood.Zach Klippenstein (he/him) [MOD]
12/04/2020, 7:38 PMupdateData
is a side effect, you shouldn’t call it inside a LaunchedEffect
. You should use rememberCoroutineScope
to get a scope scoped to the composition, and then call scope.launch{}
to kick off the side effect (e.g. from an event handler or something)Bradleycorn
12/04/2020, 7:43 PMrememberCoroutineScope
and I came across this bit:
Use this scope to launch jobs in response to callback events such as clicks or other user interaction where the response to that event needs to unfold over time and be cancelled if the composable managing that process leaves the composition. Jobs should never be launched into any coroutine scope as a side effect of composition itself. For scoped ongoing jobs initiated by composition, see [LaunchedEffect].I figured, well I’m not doing this in response to some callback or event. And I AM wanting a “scoped ongoing job initiated by composition”, so I went down the path of
LaunchedEffect
… And then in the end I wound up going to produceState
cause at the end of it all, I want a State
value to work with.updateData
method that gets called could certain be considered a side effect, as it fetches data from a network and updates a local database … But that work has to be triggered from somewhere … I could hoist it all the way up into the activity and out of the compose tree all-together, but then I’m loading the data every time the activity launches, whether the user ever ends up needing/wanting/viewing it or not. That’s not right.
So, I’ve ended up with architecture in which i have a “Screen” Composable. It uses produceState
(or LaunchedEffect
or rememberCoroutineScope
) to do that work, and then call a “Content” composable that IS idempotent and pass it the data when it’s available.
@Composable fun MyScreen(viewModel: ViewModel) {
val myData = produceState(initalValue = .., viewModel) { ... }
when (myData) {
is Loading -> LoadingComposable()
is Error -> ErrorComposable()
else -> MyContentComposable(myData.data)
}
}
Zach Klippenstein (he/him) [MOD]
12/04/2020, 8:17 PMLaunchedEffect
makes senseBradleycorn
12/04/2020, 8:18 PMSean McQuillan [G]
12/05/2020, 12:30 AMproduceState
The big thing to avoid is something like this:
@Composable
fun MyComposable() {
val scope = rememberCoroutineScope()
if (somethingIsTrue) {
scope.launch { whoKnowsHowManyTimes() }
}
}
Bradleycorn
12/05/2020, 12:38 AM