Search code examples
androidandroid-fragmentsandroid-architecture-componentsandroid-viewmodel

Android returning different instance of ViewModel for the same fragment when calling ft.replace


I have a container Fragment, call it ContainerFragment holding two fragments, FragmentA and FragmentB. Both, FragmentA and FragmentB create a ViewModel in the onCreate() method using the ViewModelProviders.of(this) approach.

Let's assume that container can only show one fragment at a time and I'm using the FragmentTransaction.replace() method to switch between FragmentA and FragmentB. In my case, I show FragmentA, which creates the FragmentA ViewModel and then an action triggers FragmentB to replace FragmentA. Now, when I'm done with FragmentB, I call replace again to show FragmentA. Here is the check I do in that method that replaces the fragments:

if (fragmentA == null) {
    fragmentA = FragmentA.newInstance();
}
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.container_content, fragmentA, "my-tag");
ft.commit();

Since the FragmentA was created the first time it ran, it doesn't go into the if block. But what I noticed that the onCreate() of FragmentA returns a different instance of the ViewModel. I have the following code in FragmentA:

public void onCreate(Bundle sI) {
    super.onCreate(sI);
    mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    Log.i("test", "ViewModel-> " + mViewModel.toString());
}

The log prints:

ViewModel-> com.myapp.MyViewModel@ed93f63

the first time I create FragmentA and then

ViewModel-> com.myapp.MyViewModel@ff3eee4

after I call ft.replace().

So, I'm confused as to why would ViewModelProviders return a different instance of the ViewModel the second time around even though FragmentA is not null (since it doesn't enter the if block, I assume it is not null)?


Solution

  • According to the doc:

    A ViewModel is always created in association with a scope (an fragment or an activity) and will be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.

    The scope of ViewModel is bound to the scope of input Fragment / Activity.

    • Activity: when it is destroyed & not caused by configuration change
    • Fragment: when it is destroyed & not caused by configuration change (i.e. removed from FragmentManager)

    In other words, the FragmentA is destroyed when you replace it with FragmentB, so its associated ViewModel instance is removed.


    When you call ViewModelProviders.of(fragment) to get a ViewModel, at the end it hands off this task to ViewModelStore, which stores our ViewModel instances.

    And let us deep dive into the source code of androidx.fragment.app.Fragment-1.0.0

    @CallSuper
    public void onDestroy() {
        mCalled = true;
        FragmentActivity activity = getActivity();
        boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
        if (mViewModelStore != null && !isChangingConfigurations) {
            mViewModelStore.clear();
        }
    }
    

    Therefore the ViewModel instance is removed because of this call mViewModelStore.clear();