Bradleycorn
11/15/2021, 5:20 PMLaunchedEffect
along with LocalLifecycleOwner
in a “Screen” composable to start a long-running/intensive coroutine in my ViewModel only when the containing Activity/Fragment is RESUMED
. More info and code in the 🧵
I’m curious if this is a proper/best solution for my use case or if there are better ways to do this?Bradleycorn
11/15/2021, 5:21 PM@Composable
fun MyScreen(viewModel: MyScreenViewModel) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(key1 = viewModel) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.pollData()
}
}
MyScreenContent(viewModel.uiState)
}
Bradleycorn
11/15/2021, 5:28 PMviewModel.pollData()
is a suspend function that polls a network API every 30 seconds to get data updates. They get put into a Room DB, and updates to the DB trigger updates to the ViewModel’s uiState
.
Because it’s doing network polling very often, I only want the polling to run while the user actually using the app, so that I’m not needlessly draining the user’s battery and data plan while the app is backgrounded, or otherwise not being used. When this screen was built using the old View system, I would launch a coroutine for the polling whenever the Fragment was in the RESUMED state, and cancel it in the PAUSED state. That ensures that I am only polling when absolutely necessary.
The above code is how I implemented similar functionality with compose.Tash
11/15/2021, 9:35 PMTash
11/15/2021, 9:40 PMpollData()
as part of a separate implementation of LifecycleObserver
that you just attach to the host Activity/Fragment, and leave it out of the ViewModel
/ rendering layersBradleycorn
11/15/2021, 10:35 PMMyScreenViewModel
could definitely be an interface, and it already does the 2nd thing. The logic for the polling is encapsulated in a very top level composable for this Screen, and that composable only does only this one thing and then calls the “Content” composable which only receives state that it needs to draw itself and no view model at all.
I agree that in general, decoupling the ViewModel and the composables that render content as much as possible is a great idea. But at very high levels (such as the composable that is called from the NavigationGraph when a particular route is given to the NavController and is the root of an entire Screen), there is a coupling of the ViewModel, which provides the state that is then handed down the tree to child composables that render the ui that makes up the screen.
My question is more abstract really than ViewModels …
First, I have some producer that produces state that is passed to a composable, and that producer is long running and resource intensive, so I want to make sure that it only runs while the composable is in the composition. LaunchedEffect
gives me that. I think that is the best way to handle that part of the problem, but I’m curious if there are other/better ways.
Second, there are times when the composable is in the composition (so the LaunchedEffect is NOT canceled), but is not visible and as such, the producer should not be running. An example is when an app is put into the background. So, to handle that, I use the LifeCycleOwner.current
to cancel/restart the producer when the Lifecycle exits/re-enters the RESUMED state. That works for that part of the problem, but I’m curious if there are other ways to do that as well.tad
11/16/2021, 2:56 AMFlow
that suspends while the lifecycle is not resumed, and collect that in the Composable with Flow.collectAsState
. But I prefer LaunchedEffect myself.Alex
11/16/2021, 7:51 AMAlex
11/16/2021, 8:04 AM