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

2

There are 2 best solutions below

0
On BEST ANSWER

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, that Fragment is added to the back stack.
When you navigate back, the current Fragment is popped off the back stack, and the previous Fragment is recreated.

However, if you are scoping your ViewModel to the NavController's lifecycle, the ViewModel will not be recreated along with the Fragment; instead, the existing ViewModel instance will be reused.

When we talk about scoping a ViewModel to a Fragment's lifecycle or to a NavController's lifecycle, we are talking about when the ViewModel is created and destroyed.

  • If the ViewModel is scoped to the Fragment's lifecycle (using viewLifecycleOwner), the ViewModel will be created when the Fragment's view is created and destroyed when the Fragment's view is destroyed. This is true whether the Fragment 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 the NavController's lifecycle (the default behavior when using ViewModelProvider(this) in a Fragment), the ViewModel will be created the first time the Fragment is launched and will not be destroyed until the NavController is destroyed (which usually happens when the activity hosting the NavController is finished). This means that the ViewModel will survive even if the Fragment is destroyed and recreated, such as when the user navigates away from the Fragment and then back to it, causing the Fragment 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 the ViewModel than a standalone Fragment would. In other words, even though your Fragment is being recreated, the associated ViewModel is not.

(See also "Get started with the Navigation component")

This is because NavHostFragment maintains its own ViewModelStore that is scoped to the NavController's lifecycle. This ViewModelStore is used to store ViewModels for all destinations (fragments) that are a part of the NavController.

(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), the ViewModel is being retrieved from the ViewModelStore associated with the NavController, not the individual fragment. This means that the ViewModel will persist as long as the NavController 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:

Log.i("CHEOK", "DashboardViewModel constructed. ID: ${System.identityHashCode(this)}")

You will see that the ID remains the same across different instances of the Fragment, indicating that it is the same ViewModel instance.


If you want a ViewModel that is scoped to the NavController's lifecycle and survives configuration changes and navigation between Fragments, you can use navGraphViewModels:

class DashboardFragment : Fragment() {

    private val dashboardViewModel: DashboardViewModel by navGraphViewModels(R.id.nav_graph) { ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!) }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Use the ViewModel
    }
}

Here, R.id.nav_graph is the ID of your navigation graph. This ViewModel will be shared between all Fragments in the navigation graph and will be destroyed when the last Fragment 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 the DashboardFragment.

The navGraphViewModels delegate method creates or retrieves a ViewModel that is scoped to a navigation graph. This means the ViewModel will be shared between all fragments in the navigation graph and will survive configuration changes and navigation between fragments. The ViewModel 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 the Fragment's lifecycle instead of the NavController's lifecycle, you would have to use the ViewModelProvider with the Fragment's viewLifecycleOwner:

val dashboardViewModel = ViewModelProvider(viewLifecycleOwner).get(DashboardViewModel::class.java)

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:

dashboardViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)).get(DashboardViewModel::class.java)

With this referring to the Fragment itself. ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!) is the Factory that is used to create the ViewModel. This factory requires the Application as a parameter, and it is retrieved from the Fragment's associated Activity.

Those codes will give you a ViewModel that is scoped to the Fragment's lifecycle and will be recreated every time the Fragment 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.

0
On

Because Dashboard Fragment is located in back stack. It has been created at least once. It includes the concept of Multiple BackStacks.

In some cases, your app might need to support multiple back stacks. A common example is if your app uses a bottom navigation bar. FragmentManager lets you support multiple back stacks with the saveBackStack() and restoreBackStack() methods. These methods let you swap between back stacks by saving one back stack and restoring a different one.