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 PMLaunchedEffectBradleycorn
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 🙈Zach Klippenstein (he/him) [MOD]
12/04/2020, 7:24 PMZach Klippenstein (he/him) [MOD]
12/04/2020, 7:25 PMproduceState 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()
}Bradleycorn
12/04/2020, 7:28 PMproduceState solution seems more concise and readable to me, and like you said, it’s essentially doing the same thing under the hood.Bradleycorn
12/04/2020, 7:29 PMZach 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.Bradleycorn
12/04/2020, 7:59 PMupdateData 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 AMSean McQuillan [G]
12/05/2020, 12:31 AMSean McQuillan [G]
12/05/2020, 12:37 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 AMBradleycorn
12/05/2020, 12:42 AM