The documentation https://developer.android.com/topic/libraries/architecture/viewmodel#sharing describes how we can share the same ViewModel across the different Fragments.
I have some complicated pages in my single Activity app with a container and tabs fragments. Each such page has own ViewModel which should be shared with all contained fragments.
The key trick here is to use Activity instead of Fragment to hold my ViewModel.
The problem is that my Activity can have multiple pages with own models and holding the view model for particular page all the time is waste of device resources.
Is there any way to control the life-cycle of ViewModel to destroy it when user leaves the page?
I thought to use the container fragment instead of Activity:
model = ViewModelProviders.of(getPageContainerFragment()).get(SharedViewModel.class);
But found this idea not so good because all children fragments should know about the parent which could be not so good.
Is there any alternatives to tackle properly such case?
The lifecycle of a ViewModel extends from when the associated UI controller is first created, till it is completely destroyed. Never store a UI controller or Context directly or indirectly in a ViewModel. This includes storing a View in a ViewModel.
According to docs, viewmodel remains in memory until activity finish or fragment deattach. They also give example with figure showing viewmodel survive in configuration change but destroy after finish() method call. But while configuration changes there is ondestroy() method.
In android, we can use ViewModel to share data between various fragments or activities by sharing the same ViewModel among all the fragments and they can access everything defined in the ViewModel. This is one way to have communication between fragments or activities.
ViewModel is a separate class which has an instance of it in the activity and has no reference for view in it. So even though activity onDestroy() and onCreate() happens on configuration change, the instance of ViewModel doesn't get destroyed or garbage collected.
If I get it right, your question is "how to free up resources" not "how to clear viewmodel".
So, you can make your viewmodels as light as possible, like this:
abstract class MyViewModel: ViewModel() {
abstract fun freeResources()
}
and call vm.freeResources()
in your OnPageChangeListener
or OnTabSelectedListener
or whichever listener you use, when page is changed.
In this case your should obtain viewModel using activity scope.
Alternatively, if you really want your viewmodel to be onCleared()
and then the new one created, I can suggest using scoped-vm library. It allows your to request viewmodels for a scope identified by a string name.
ScopedViewModelProviders
.forScope(fragment, "scope")
.of(activity)
.get(MyViewModel::class.java)
Scope gets cleared (so are viewmodels in it) as soon as last fragment that requested something from that scope gets destroyed. So use different scopes for your pages.
But, in this case you should double-check the lifecycle of your fragments: if your PagerAdapter holds them for re-use, the scope will never be cleared, and only manual approach will help you.
Since you are using Android Jetpack, I can assume that you also use Navigation Component.
If you want a ViewModel to only stay active when you are in certain fragments, you can create a navigation chart for those fragments, so that the shared ViewModel only lives while you are browsing between those fragments and is destroyed when you leave them.
Imagine that your app has these fragments,
And you want to keep a ViewModel alive while you are browsing between Fragment Vehicles and its different tabs.
Well, create a nested navigation chart for them like this.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_navigation.xml"
app:startDestination="@id/MainFragment">
<fragment
android:id="@+id/MainFragment"
android:name="com.fortatic.apps.guesstheword.ui.welcome.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_vehicleGraph"
app:destination="@id/vehicleGraph" />
</fragment>
<navigation
android:id="@+id/vehicleGraph"
app:startDestination="@id/vehicleFragment" >
<fragment
android:id="@+id/vehicleFragment"
android:name="com.fortatic.apps.guesstheword.ui.game.VehicleFragment"
android:label="VehicleFragment"
tools:layout="@layout/fragment_vehicle">
<action
android:id="@+id/action_fragmentVehicle_to_sedanFragment"
app:destination="@id/sedanFragment"/>
<action
android:id="@+id/action_fragmentVehicle_to_pickupsFragment"
app:destination="@id/pickupsFragment"/>
<action
android:id="@+id/action_fragmentVehicle_to_offroadFragment"
app:destination="@id/offroadFragment"/>
</fragment>
<fragment
android:id="@+id/sedanFragment"
android:name="com.fortatic.apps.guesstheword.ui.score.SedanFragment"
android:label="SedanFragment"
tools:layout="@layout/fragment_sedan">
...
</fragment>
<fragment
android:id="@+id/pickupsFragment"
android:name="com.fortatic.apps.guesstheword.ui.score.PickupFragment"
android:label="PickupFragment"
tools:layout="@layout/fragment_pickups">
...
</fragment>
<fragment
android:id="@+id/offroadFragment"
android:name="com.fortatic.apps.guesstheword.ui.score.OffroadFragment"
android:label="OffroadFragment"
tools:layout="@layout/fragment_offroad">
...
</fragment>
</navigation>
</navigation>
Once you have created the nested navigation graph, simply request an instance of ViewModel using:
private val mySharedViewModel: SharedViewModel by navGraphViewModels(R.id.myNestedGraph) {
//defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.
}
You can find more details in this answer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With