I am referring to the Bottom Navigation Views Activity template project created by Android Studio.
I notice, whenever I tap on the bottom view to switch from DashboardFragment page, to another fragment page. Then, I tap on the bottom view again to switch back to DashboardFragment, a new DashboardFragment will be constructed.
I have verified such a behaviour by having logging in its init function.
DashboardFragment.kt
class DashboardFragment : Fragment() {
...
init {
Log.i("CHEOK", "DashboardFragment constructed")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Log.i("CHEOK", "ViewModelProvider(this).get in DashboardFragment onCreateView")
val dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
DashboardViewModel.kt
class DashboardViewModel : ViewModel() {
...
init {
Log.i("CHEOK", "DashboardViewModel constructed")
}
}
However, to my surprise, DashboardViewModel, which is a DashboardFragment owned ViewModel, is not being re-constructed.
Here's is the logging I am observing, when DashboardFragment is switched to visible for the 1st time.
DashboardFragment constructed
ViewModelProvider(this).get in DashboardFragment onCreateView
DashboardViewModel constructed
When I switch to another fragment, and then switch back to DashboardFragment for the 2nd time, the following is the logging I am observing
DashboardFragment constructed
ViewModelProvider(this).get in DashboardFragment onCreateView
I am expecting DashboardViewModel will be re-created again. This is because, ViewModelProvider is having DashboardFragment as its owner, and not MainActivity.
val dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
When the 1st DashboardFragment is destroyed, the DashboardViewModel should be destroyed too. But, seems like this is not the case.
May I know, why ViewModel is not re-created even though its owner (Fragment) is being re-created in NavHostFragment?
Demo
You may test the demo at https://github.com/yccheok/lifecycle-NavHostFragment/tree/f58b7aa6773de09811e9858a84a3b4614edbe3b3
To expand on Arda Kazancı's answer, the back stack is a record of the navigation history within a
NavController.Each time you navigate to a
Fragment, thatFragmentis added to the back stack.When you navigate back, the current
Fragmentis popped off the back stack, and the previousFragmentis recreated.However, if you are scoping your
ViewModelto theNavController's lifecycle, theViewModelwill not be recreated along with theFragment; instead, the existingViewModelinstance will be reused.When we talk about scoping a
ViewModelto aFragment's lifecycle or to aNavController's lifecycle, we are talking about when theViewModelis created and destroyed.If the
ViewModelis scoped to theFragment's lifecycle (usingviewLifecycleOwner), theViewModelwill be created when theFragment's view is created and destroyed when theFragment's view is destroyed. This is true whether theFragmentis destroyed because it was popped off the back stack or because of a configuration change like a screen rotation.If the
ViewModelis scoped to theNavController's lifecycle (the default behavior when usingViewModelProvider(this)in aFragment), theViewModelwill be created the first time theFragmentis launched and will not be destroyed until theNavControlleris destroyed (which usually happens when the activity hosting theNavControlleris finished). This means that theViewModelwill survive even if theFragmentis destroyed and recreated, such as when the user navigates away from theFragmentand then back to it, causing theFragmentto be popped off the back stack and then added back to it.When you are using the Navigation component along with
NavHostFragment, it uses a slightly different lifecycle scope for theViewModelthan a standaloneFragmentwould. In other words, even though yourFragmentis being recreated, the associatedViewModelis not.(See also "Get started with the Navigation component")
This is because
NavHostFragmentmaintains its ownViewModelStorethat is scoped to theNavController's lifecycle. ThisViewModelStoreis used to storeViewModelsfor all destinations (fragments) that are a part of theNavController.(See also "Communication of Fragments in the Fragment, using Navigation Component, MVVM and Koin" by Piotr Prus)
When you are calling
ViewModelProvider(this).get(DashboardViewModel::class.java), theViewModelis being retrieved from theViewModelStoreassociated with theNavController, not the individual fragment. This means that theViewModelwill persist as long as theNavControlleris alive, even if individual fragments are destroyed and recreated.This design allows data to be persisted across configuration changes and navigation events, which is a common pattern in Android development.
You can observe this behavior by checking the ID of the
ViewModelobject:You will see that the ID remains the same across different instances of the
Fragment, indicating that it is the sameViewModelinstance.If you want a
ViewModelthat is scoped to theNavController's lifecycle and survives configuration changes and navigation betweenFragments, you can usenavGraphViewModels:Here,
R.id.nav_graphis the ID of your navigation graph. ThisViewModelwill be shared between allFragmentsin the navigation graph and will be destroyed when the lastFragmentin the navigation graph is popped off the back stack.(See also "What is the difference between navGraphViewModels and activityViewModels?")
But then, the original behavior you observed will persist. The
DashboardViewModelwill not be re-constructed each time you navigate back to theDashboardFragment.The
navGraphViewModelsdelegate method creates or retrieves aViewModelthat is scoped to a navigation graph. This means theViewModelwill be shared between all fragments in the navigation graph and will survive configuration changes and navigation between fragments. TheViewModelwill only be cleared (and thus ready for re-construction) when the last fragment in the navigation graph is popped off the back stack.If you want the
ViewModelto be scoped to theFragment's lifecycle instead of theNavController's lifecycle, you would have to use theViewModelProviderwith theFragment'sviewLifecycleOwner:Note: as noted in "ViewModelProviders is deprecated in 1.1.0", early in 2020, Google had deprecated the ViewModelProviders class, in version 2.2.0 of the AndroidX lifecycle library.
In the context of the AndroidX libraries, the equivalent would be:
With
thisreferring to theFragmentitself.ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)is theFactorythat is used to create theViewModel. This factory requires theApplicationas a parameter, and it is retrieved from theFragment's associatedActivity.Those codes will give you a
ViewModelthat is scoped to theFragment's lifecycle and will be recreated every time theFragmentis recreated.However, keep in mind that this might not be the desired behavior, especially if you want to maintain state across navigation events.
Illustration: "How Android ViewModel works under the hood to survive to configuration change" by Torcheux Frédéric.