Issue
Up until now I used to collect my flows either in activity/fragment or in ViewModel like so
Activity/Fragment
lifecycleScope.launch {
myViewModel.readTokenCredentials().collect { data -> /* do something */ }
}
ViewModel
viewModelScope.launch {
prefsRepo.readTokenCredentials().collect { data -> /* do something */ }
}
Now Google devs tell us that this is not a safe way to collect flows because it could lead to memory leaks. Instead they recommend wrapping the collection in lifecycle.repeatOnLifecycle
for flow collection in Activities/Fragments.
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
myViewModel.readTokenCredentials().collect { data -> /* do something */ }
}
}
My question is:
Why can't I use repeatOnLifecycle
with viewModelScope
when collecting flows inside the view model? Of course I know view model is not lifecycle aware but is perhaps viewModelScope
less likely to introduce memory leaks during flow collection?
Solution
It's not possible to have a repeat on lifecycle since the ViewModel doesn't have a repeating lifecycle. It starts once and is destroyed once.
I don't think memory leak is an accurate term for what's happening when a Flow continues being collected while a Fragment is off-screen. It's just causing its upstream Flow to keep emitting for no reason, but the emitted items will be garbage collected. It's simply a waste of activity. The danger comes if you are also updating UI in the collector because you can accidentally update views that are off-screen.
In a ViewModel, you have the same risk of collecting from Flows for no reason. To avoid it, you can use stateIn
or shareIn
with a WhileSubscribed
value. Then it will stop collecting when there is nothing downstream collecting. And if you're using repeatOnLifecycle
in your Activities and Fragments that collect from these SharedFlows and StateFlows, then everything's taken care of.
For example:
val someFlow = prefsRepo.readTokenCredentials()
.map { data -> // doSomething }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 1)
And collect it in the UI layer. If there's nothing for the UI to collect, then why does the Flow exist in the first place? I can't think of a good counter-example. ViewModel is intended for preparing the model for viewing, not doing work that's never been seen.
Answered By - Tenfour04
Answer Checked By - Pedro (JavaFixing Volunteer)