Reputation: 2838
TL;DR
For one instance of a fragment, that is added/removed again and again, viewModelScope
in its ViewModel only works until the first time fragment is popped. After this, viewModelScope
becomes inactive. How can I reinitialize the viewModelScope
when I add the fragment back? Or Should I look for a better implementation to keep it active?
Scenario:
I have used RxJava
through ViewModel
to retrieve data from network in an old project, and to send the updates to UI, I have used LiveData.
Following is how I initialised a typical ViewModel inside a fragment:
private val fruitsViewModel by viewModels<FruitsViewModel>()
Following is the above mentioned ViewModel:
class FruitsViewModel: ViewModel {
private var category: String = FruitCategories.DEFAULT
private val _fruits = MutableLiveData<List<Fruit>>()
fun fruits(): LiveData<List<Fruit>> = _fruits
fun updateCategory(newCategory: String) {
this.category = newCategory
fetchFruits()
}
fun fetchFruits() { ... }
}
In Fragment's onViewCreated()
, I called fruitsViewModel.fetchFruits()
. This worked great(without any visible problems).
Today, I switched RxJava specific code with coroutines to retrieve network data in my ViewModel. Something like this:
fun fetchFruits() {
/* this.fruitsJob = */
viewModelScope.launch {
...
}
}
This works good too, even after I replace a fragment and come back to it. But, if I keep a fragment instance declared in Activity, and use it again, viewModelScope.launch {}
doesn't work anymore. Something like this:
class FruitsActivity: AppCompatActivity() {
private val fruitsFragment = FruitsFragment()
private fun showFruitsFragment() {
supportFragmentManager
.beginTransaction()
.replace(getFragmentContainerId(), fruitsFragment)
.addToBackStack(fruitsFragment.tag)
.commit()
}
private fun removeFragments() {
val manager = supportFragmentManager
if (manager.backStackEntryCount > 0) {
val first: FragmentManager.BackStackEntry = manager.getBackStackEntryAt(0)
manager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
}
The reason for keeping the fragment instance is to load API data only once, and after that, it should just appear on the click of a button without any flicker/loading.
After I call removeFragments()
, fruitsViewModel
has already called its onCleared()
for kept declaration of fruitsFragment
. So, its viewModelScope
becomes inactive, and if I add fruitsFragment
back again by calling showFruitsFragment()
, this will not retrieve network data anymore. This was not the case for my Rxjava implementation because I didn't use viewModelScope
.
I think I can just fix the problem by linking fruitsViewModel
to FruitsActivity
's instance instead. But, I want to know if this can be considered as the best way.
Upvotes: 1
Views: 1385
Reputation: 7642
Yes, you should scope your viewModel
differently, in your case scoping fruitsViewModel
to FruitsActivity
makes complete sense.
I'm not sure how your RxJava
code worked, without seeing the code I'm guessing, that it has something to do with it not being disposed
correctly.
Also, even if you would create a scope, that outlives the viewModelScope
(for ex: create and maintain a CoroutineScope
in the activity) my main issue is that approach is not intuitive/natural, people will have a harder time checking how & where is that CoroutineScope
maintained
Upvotes: 1