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
, thatFragment
is added to the back stack.When you navigate back, the current
Fragment
is popped off the back stack, and the previousFragment
is recreated.However, if you are scoping your
ViewModel
to theNavController
's lifecycle, theViewModel
will not be recreated along with theFragment
; instead, the existingViewModel
instance will be reused.When we talk about scoping a
ViewModel
to aFragment
's lifecycle or to aNavController
's lifecycle, we are talking about when theViewModel
is created and destroyed.If the
ViewModel
is scoped to theFragment
's lifecycle (usingviewLifecycleOwner
), theViewModel
will be created when theFragment
's view is created and destroyed when theFragment
's view is destroyed. This is true whether theFragment
is destroyed because it was popped off the back stack or because of a configuration change like a screen rotation.If the
ViewModel
is scoped to theNavController
's lifecycle (the default behavior when usingViewModelProvider(this)
in aFragment
), theViewModel
will be created the first time theFragment
is launched and will not be destroyed until theNavController
is destroyed (which usually happens when the activity hosting theNavController
is finished). This means that theViewModel
will survive even if theFragment
is destroyed and recreated, such as when the user navigates away from theFragment
and then back to it, causing theFragment
to 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 theViewModel
than a standaloneFragment
would. In other words, even though yourFragment
is being recreated, the associatedViewModel
is not.(See also "Get started with the Navigation component")
This is because
NavHostFragment
maintains its ownViewModelStore
that is scoped to theNavController
's lifecycle. ThisViewModelStore
is used to storeViewModels
for 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)
, theViewModel
is being retrieved from theViewModelStore
associated with theNavController
, not the individual fragment. This means that theViewModel
will persist as long as theNavController
is 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
ViewModel
object:You will see that the ID remains the same across different instances of the
Fragment
, indicating that it is the sameViewModel
instance.If you want a
ViewModel
that is scoped to theNavController
's lifecycle and survives configuration changes and navigation betweenFragments
, you can usenavGraphViewModels
:Here,
R.id.nav_graph
is the ID of your navigation graph. ThisViewModel
will be shared between allFragments
in the navigation graph and will be destroyed when the lastFragment
in 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
DashboardViewModel
will not be re-constructed each time you navigate back to theDashboardFragment
.The
navGraphViewModels
delegate method creates or retrieves aViewModel
that is scoped to a navigation graph. This means theViewModel
will be shared between all fragments in the navigation graph and will survive configuration changes and navigation between fragments. TheViewModel
will 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
ViewModel
to be scoped to theFragment
's lifecycle instead of theNavController
's lifecycle, you would have to use theViewModelProvider
with 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
this
referring to theFragment
itself.ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)
is theFactory
that is used to create theViewModel
. This factory requires theApplication
as a parameter, and it is retrieved from theFragment
's associatedActivity
.Those codes will give you a
ViewModel
that is scoped to theFragment
's lifecycle and will be recreated every time theFragment
is 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.