preface

ViewModel as the star component of Jetpack, I believe you all have a certain understanding of it. The official introduction of Google also lists the advantages of ViewModel in detail, such as:

  1. Provides and manages UI data. Decouple load data from data recovery from Activity or Fragment
  2. Lifecycle aware components.
  3. It will not be destroyed due to configuration changes.
  4. It can be used with LiveData.
  5. Multiple fragments can share the same ViewModel.
  6. Etc etc…

You can also see the ViewModel in more detail in the following two videos:

  • ViewMode male explanatory version
  • ViewMode for girls

In this article, I won’t explain how viewModels are used or why, but rather how they work. By reading this article you can learn:

  • The binding process of a ViewModel in an Activity.
  • ViewModel is not destroyed in an Activity due to configuration changes.
  • The binding process of viewModels in fragments.
  • Viewmodels in fragments will not be destroyed due to configuration changes.
  • ViewMode can be shared in fragments.

Hopefully, this article will give you a deeper understanding of the ViewModel.

The binding process of a ViewModel to an Activity

When using ViewModel, we declare our own ViewModel first and create the ViewModel using ViewModel providers in the onCreate method of our Activity. The following code looks like this:

 MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
Copy the code

In Google’s latest code, it is not recommended to use ViweModelProviders and instead create ViewModelProvider objects directly using the ViewModelProvider constructor.

Using the of() method of the ViewModelProviders class, we get a ViewModelProvider object. The following code looks like this:

   public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return new ViewModelProvider(activity);
    }
Copy the code

The ViewModelProvider class requires us to pass the ViewModelStore and Factory objects. Its constructor is declared as follows:

    // Use the ViewModelStoreOwner object constructor
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

    // Use the constructors of the ViewModelStoreOwner and Factory objects
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    // Use the constructors of the ViewModelStore and Factory objects
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
Copy the code

Inside the ViewModelProvider, there are three types of constructors:

  • (ViewModelStoreOwner owner):
    • This constructor uses the owner object’sgetViewModelStore()Method to obtainViewModelStoreObject if the owner object passed in is also implementedHasDefaultViewModelProviderFactoryInterface, then is calledgetDefaultViewModelProviderFactory()The Factory () method gets the Factory. Instead, use internal staticNewInstanceFactoryObject to create the Factory object.
  • (ViewModelStoreOwner owner, Factory factory):
    • This constructor uses the owner object’sgetViewModelStore()Method to obtainViewModelStoreObject, using the passed Factory object
  • (ViewModelStore store, Factory factory):
    • useViewModelStoreFactoryObject constructor

Factory Interface

In ViewModelProvider, Factory is used to create viewModels. Factory is declared as follows:

    public interface Factory {
        /** * Creates a ViewModel object from the given Class object * <p> **@paramThe Class object of the ViewModel required by modelClass *@param<T> ViewModel's generic arguments *@returnThe newly created ViewModel object */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }
Copy the code

By implementing the Factory interface, we can implement the Factory we want to create the ViewModel we need. There are several classes in Android that implement this interface (e.g. KeyedFactory, AndroidViewModelFactory). Here we use the default NewInstanceFactory:

    public static class NewInstanceFactory implements Factory {

        private static NewInstanceFactory sInstance;

        @NonNull
        static NewInstanceFactory getInstance(a) {
            if (sInstance == null) {
                sInstance = new NewInstanceFactory();
            }
            return sInstance;
        }

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            try {
                // The default constructor for the corresponding ViewModel class takes no arguments to create instance objects
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}}Copy the code

By default, NewInstanceFactory calls the ViewModel’s no-argument constructor to create instance objects, although you can also pass a custom Factory if you need to use other arguments in the ViewModel.

ViewModelStore introduction

The ViewModelStore maintains a HashMap whose key is DEFAULT_KEY + the name of the underlying Class specification of the ViewModel object, and whose value is the corresponding ViewModel object. Each Activity and Fragment corresponds to a ViewModelStore, which stores the required ViewModels. The ViewModelStore class declaration looks like this:

DEFAULT_KEY value is: “androidx. Lifecycle. ViewModelProvider. DefaultKey”

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys(a) {
        return new HashSet<>(mMap.keySet());
    }

    /** * When the internal ViewModel is no longer used, the memory is cleared */
    public final void clear(a) {
        for (ViewModel vm : mMap.values()) {
            // Call the ViewModel clear methodvm.clear(); } mMap.clear(); }}Copy the code

Create and get the ViewModel process in the Activity

The final creation and acquisition of the ViewModel requires the ViewProvider Class to call the Get (Class

modelClass) method. Create and save the required ViewModel object) as follows:

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
Copy the code

This method internally calls the get(String key, Class

modelClass) method:

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //👇 retrieves the corresponding ViewModel from ViewModelStore based on the key value
        ViewModel viewModel = mViewModelStore.get(key);
        //👇 determines if the Class object passed is an object of the Class Class of the ViewModel or a subclass of it, and returns it if it is
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if(viewModel ! =null) {
                // TODO: log a warning.}}//👇 if null, create a new VideModel based on the Factory passed in
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        //👇 stores the new ViewModel into the ViewModelStore and returns
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

Copy the code

In this method, the ViewModel is retrieved and saved in the ViewModelStore based on the key passed in. Its specific logic is as follows:

  • Fetch the corresponding ViewModel from ViewModelStore based on the key value.
  • Determines whether the Class object passed is an object of the ViewModel’s Class Class or a subclass of it, and returns it if it is. (whenObject.isInstance(class)The accepted parameter isnull, the method returnsfalse)
  • If the ViewModel obtained is null, a new VideModel is created based on the passed Factory object and the created ViewModel is placed in the ViewModelStore.

The overall process of creating and retrieving a ViewModel in an Activity is as follows:

ViewModel is not destroyed in an Activity due to configuration changes

We all know that the ViewModel will not be destroyed when the Activity’s configuration changes. The ViewModel scope is as follows:

Looking at the picture above, I’m sure you must have the following doubts:

  • When the Activity changes due to configuration, the system creates a new Activity. So how does the ViewModel from the old Activity get passed to the new Activity?
  • How does the ViewModel sense if the configuration has changed and decide whether to destroy it?

To solve these problems, we need to understand how data recovery works in Android and how the ViewModel actually works in the Activity lifecycle.

Common methods of data recovery

On The Android operating system, data recovery is required in the following scenarios:

  • Scenario 1: A resource-related configuration change causes the Activity to be killed and recreated.
  • Scenario 2: Low-priority activities are killed due to insufficient resource memory.

In the preceding scenario, three data recovery modes are used.

For scenario 1, the special case of configuring Android :configChanges in the manifest file is not considered.

Use onSaveInstanceState and onRestoreInstanceState

The onSaveInstanceState and onRestoreInstanceState methods handle scenario 1 and scenario 2. This can be used when your interface data is simple and lightweight, such as primitive data types or simple objects (such as Strings). If you need to recover more complex data, you should consider using ViewModle + onSaveInstanceState() (why this should be used, explained below), because using onSaveInstanceState() will result in serialization or deserialization, which, There is a certain amount of time consumption.

OnSaveInstanceState () for a more detailed introduction and use, please refer to the official documentation:

  • Use onSaveInstanceState() to save a simple, lightweight interface state
  • Restore the state of the Activity interface using the saved instance state

Use setRetainInstance of fragments

When configuration changes, the Fragment is destroyed and rebuilt along with the host Activity. When we call the setRetainInstance(True) method in the Fragment, the system allows the Fragment to bypass the destruction-rebuild process. Using this method, signals are sent to the system to keep the Fragment instance while the Activity rebuilds. Note that:

  • After using this method,Don’tCall of the fragmentsonDestory()Method, but will still be calledonDetach()methods
  • After using this method,Don’tCall of the fragmentsonCreate(Bundle)Methods. Because the Fragment was not rebuilt.
  • Fragment after using this methodonAttach(Activity)onActivityCreated(Bundle)Methods will still be called.

The following sample code shows how to retain the Fragment instance and restore the data if the configuration changes.

public class MainActivity extends AppCompatActivity {

    private SaveFragment mSaveFragment;

    public static final String TAG_SAVE_FRAGMENT = "save_fragment";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();
        mSaveFragment = (SaveFragment) fm.findFragmentByTag(TAG_SAVE_FRAGMENT);

        // The fragment is not empty because the configuration has changed and the fragment is rebuilt
        if (mSaveFragment == null) {
            mSaveFragment = SaveFragment.newInstance();
            fm.beginTransaction().add(mSaveFragment, TAG_SAVE_FRAGMENT).commit();
        }

        // Get the saved data
        intsaveData = mSaveFragment.getSaveData(); }}Copy the code

Fragments:

public class SaveFragment extends Fragment {

    private int saveData;

    public static SaveFragment newInstance(a) {
        Bundle args = new Bundle();
        SaveFragment fragment = new SaveFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Save the current Fragment instance
        setRetainInstance(true);
        saveData = 1010;// Assign the data to be saved by requesting or querying the database through the network
    }

    @Override
    public void onDetach(a) {
        super.onDetach();
    }

    public int getSaveData(a) {
        returnsaveData; }}Copy the code

For more information on how to use and note Fragments’ setRetainInstance, see Handling Configuration Changes with Fragments

Use onRetainNonConfigurationInstance with getLastNonConfigurationInstance

Provides onRetainNonConfigurationInstance method in the Activity, used for handling configuration changes data preservation. Then in recreating the Activity call getLastNonConfigurationInstance get saved data last time. We can’t override the above methods directly. If we want to customize the data we want to restore in the Activity, we need to call the internal methods of the above two methods:

  • onRetainCustomNonConfigurationInstance()
  • getLastCustomNonConfigurationInstance()

Note: System calls onRetainNonConfigurationInstance method timing between onStop – onDestory, GetLastNonConfigurationInstance method can call in onCreate and onStart method.

The following code shows how to restore custom data in Actiity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String name = (String) getLastCustomNonConfigurationInstance();
        if(! TextUtils.isEmpty(name)) {// Obtain the restored data and perform related operations}}// You can retrieve the recovered data in onStart
// @Override
// protected void onStart() {
// super.onStart();
// String name = (String) getLastCustomNonConfigurationInstance();
// if (! TextUtils.isEmpty(name)) {
/ /}
/ /}

    @Nullable
    @Override
    public Object onRetainCustomNonConfigurationInstance(a) {
        return "AndyJennifer"; }}Copy the code

After Android 3.0, it is officially recommended to use Fragment#setRetainInstance(true) for data recovery. My guess is that this approach is recommended to reduce the redundancy of the Activity and take the task of data recovery out of the Activity, which is more in line with the single-responsibility design pattern.

Summary of several data recovery methods

By understanding several ways of data recovery, we can get the following comparison diagram:

The recovery of the ViewModel

The ViewModel was officially designed with a preference for data recovery when configuration changes were made. Considering the efficiency of data recovery, officials eventually adopted onRetainNonConfigurationInstance way to restore the ViewModel.

Until SDK 27, data was recovered using Fragment#setRetainInstance(true). The reason for the official revision of the internal implementation is the Fragment pit, the extensibility of the program and other factors.

Now that we know how to restore the ViewModel, let’s solve our previous puzzle. When the Activity changes due to configuration, the system creates a new Activity. How does the ViewModel from the old Activity get passed to the new Activity?

In the latest code, the Activity of Androidx official rewrite the onRetainNonConfigurationInstance method, This method saves the ViewModelStore (ViweModelStore stores the ViewModel), which in turn saves the ViewModel as follows:

    public final Object onRetainNonConfigurationInstance(a) {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) { viewModelStore = nc.viewModelStore; }}if (viewModelStore == null && custom == null) {
            return null;
        }

        / / will be stored in NonConfigurationInstances ViewModel objects
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
Copy the code

When the new Activity is recreated and calls viewModelproviders.of (this).get(xxxModel.class), The ViewModelStore saved by the old Activity is retrieved in the getViewModelStore() method. So you’ve got the ViewModel. The specific code is as follows:

    public ViewModelStore getViewModelStore(a) {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            / / 👇 save NonConfigurationInstances,
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                //👇 gets the ViewModelStore from this object
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = newViewModelStore(); }}return mViewModelStore;
    }
Copy the code

When the ViewModel determines whether or not it is removed

The most important feature of the ViewModel is that it cannot be removed when configuration changes. The internal implementation is also very simple, listening for the Activity declaration cycle and determining if the configuration has changed when the onDestory method is called. If no changes are sent, all viewModels are cleared by calling the ViewModelStore clear() method in the Activity. The specific code is as follows:

    public ComponentActivity(a) {
        Lifecycle lifecycle = getLifecycle();
        // omit more....
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if(! isChangingConfigurations()) {//👇 clear all viewModels when the configuration has not changed and the onDestory method is reachedgetViewModelStore().clear(); }}}}); }Copy the code

ViewModel binding process in Fragment

In the latest official code implementation, the ViewModel in a Fragment is closely related to its host Activity. To understand the process of binding ViewModel to Fragment, we need to understand FragmentManager and FragmentManagerViewModel.

FragmentManager introduction

Each Fragment and host Activity (inherited from FragmentActivity) initializes a FragmentManager object at creation time. The key to understanding the connection between a ViewModel and an Activity in a Fragment is to sort out these different classes of stack views.

Here is a brief diagram:

  • For the host Activity,getSupportFragmentManager()You get the FragmentManager object for the FragmentActivity;
  • The fragments,getFragmentManager()Is the FragmentManager object of the parent Fragment (or FragmentActivity if none) retrieved, andgetChildFragmentManager()Is to get its own FragmentManager object.

FragmentManagerViewModel introduction

When each Fragment is created, a FragmentManagerViewModel object is created, which stores the ViewModelStore and FragmentManagerViewMoel of its child fragments. The specific structure is as follows:

In FragmentManagerViewModel:

  • MViewModelStore is of type<String, FragmentManagerViewModel>The HashMap
  • MChildNonConfigs is of type<String, ViewModelStore>The HashMap

The keys corresponding to the two maps are the unique UUID of the Fragment. This UUID is automatically generated when the Fragment object is created. That is, each Fragment corresponds to a unique UUID.

ViewModel specifies the Fragment binding process

The binding process of ViewModel and Fragment is relatively complex, mainly divided into three processes:

  • Step 1: When the host Activity is created, it is created by defaultFramgentManagerCreate a FragmentManagerViewModel in. Also stores the generated FragmentManagerViewModel in its own ViewModelStore. Use your own FragmentManager as well
  • Step 2: When the Fragment is created, run theThe host ActivityThe father FragmentIn theFramgentManagerGets the corresponding FragmentManagerViewModel and uses its ownChildFragmentManagermNonConfigVariable to save.
  • Step 3: Associate the ViewModel created in the Fragment with its own ViewModelStore, and store its own ViewModelStore in the FragmentmNonConfigPoints to the FragmentManaerViewModelmViewModelStoresIn the.

Below I will combine the source code to carry on the detailed introduction to these three processes.

Step 1 Process

When the host Activity is created, a FragmentManagerViewModel is created by default in its FramgentManager. Also stores the generated FragmentManagerViewModel in its own ViewModelStore. Use your own FragmentManager as well

The onCreate method in FragmentActivity:

    protected void onCreate(@Nullable Bundle savedInstanceState){
        mFragments.attachHost(null /*parent*/);/ / 👈 to null
        // omit more...
    }
Copy the code

MFragments are FragmentControllers that indirectly control the FragmentManager internally through the FragmentHostCallback.

This method ends up executing the attachController method of the FragmentManager in the FragmentActivity:

 void attachController(@NonNull FragmentHostCallback<? > host, @NonNull FragmentContainer container, @Nullablefinal Fragment parent) {
        // omit more...
        if(parent ! =null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            / / 👇 go here
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false); }}Copy the code

Because parent = null is passed in and the Activity implements the ViewModelStoreOwner interface by default, the ViewModelStore in the Activity is retrieved. Next call the FragmentManagerViewModel getInstance() method:

    static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
        ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
                FACTORY);
        return viewModelProvider.get(FragmentManagerViewModel.class);
    }
Copy the code

In this method, the FragmentManagerViewModel is created and added to the ViewModelStore in the Activity.

The overall process is as follows:

Step 2 Process

When creating a Fragment, get the corresponding FragmentManagerViewModel from the FramgentManager of the host Activity or parent Fragment. And use the mNonConfig variable in its own ChildFragmentManager.

When a Fragment is associated with an Activity, in its performAttach() method

    void performAttach(a) {
        //👇 will call attachController again
        mChildFragmentManager.attachController(mHost, new FragmentContainer() {
            @Override
            @Nullable
            public View onFindViewById(int id) {
                if (mView == null) {
                    throw new IllegalStateException("Fragment " + this + " does not have a view");
                }
                return mView.findViewById(id);
            }

            @Override
            public boolean onHasView(a) {
                return(mView ! =null); }},this);//👈 note that the parent passed by this is the current Fragment
      // omit more...
    }
Copy the code

This method calls the attachController method in the Fragment ChildFragmentManager as follows:

 void attachController(@NonNull FragmentHostCallback<? > host, @NonNull FragmentContainer container, @Nullablefinal Fragment parent) {
        // omit more...
        if(parent ! =null) {
            //👆 Because parent is this, we get the Activity's FragmentManager
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(a);
        } else {
            mNonConfig = new FragmentManagerViewModel(false); }}Copy the code

Note that when the Fragment is a child Fragment, the parent. FragmentManager value is the fragmentManager of the parent Fragment; otherwise, it is the fragmentManager of the Activity.

Continue tracing the getChildNonConfig method under FragmentManager:

  private FragmentManagerViewModel getChildNonConfig(Fragment f){
        return mNonConfig.getChildNonConfig(f);
    }
Copy the code

MNonConfig itself is FragmentManagerViewModel, which we continue to trace:

  FragmentManagerViewModel getChildNonConfig(Fragment f){
        FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
        if (childNonConfig == null) {
            //👇 Create FragmentManagerViewModel of the Fragment
            childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
            mChildNonConfigs.put(f.mWho, childNonConfig);
        }
        return childNonConfig;
    }
Copy the code

In this method, the Fragment’s FragmentManagerViewModel is retrieved from mChildNonConfigs in the FragmentManagerViewModel in the Activity and returned if it has one. Otherwise, store it in mChildNonConfigs.

The overall process is as follows:

Step 3 Process

Associate the ViewModel created in the Fragment with its own ViewModelStore, The ViewModelStore is stored in mViewModelStores in the FragmentManaerViewModel pointed to by mNonConfig.

In fragments, ViewModelStore is created and retrieved through its FragmentManager. The specific code is as follows:

    public ViewModelStore getViewModelStore(a) {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }
Copy the code

Note that the value of mFragmentManager is the FragmentManager of the parent Fragment when the Fragment is a child Fragment; otherwise, it is the FragmentManager of the Activity.

Continue tracing the getChildNonConfig method under FragmentManager:

  ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }
Copy the code

MNonConfig itself is FragmentManagerViewModel, which ultimately follows the getViewModelStore method.

  ViewModelStore getViewModelStore(@NonNull Fragment f) {
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            viewModelStore = new ViewModelStore();
            // Place the created ViewStore in the FragmentManagerViewModel
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }
Copy the code

This method eventually stores the Fragment ViewModelStore into the mViewModelStores collection in FragmentManagerViewModel.

The process of creating a Fragment and getting a ViewModel is as follows:

Viewmodels in fragments will not be destroyed due to configuration changes

The reason viewModels in Fragments don’t get destroyed by configuration changes is because the declared ViewModel is stored in the FragmentManagerViewModel, The FragmentManagerViewModel is stored in the ViewModelStore of the host Activity, and the ViewModelStore of the Activity is not destroyed due to configuration changes. Therefore, the Fragment ViewModel will not be destroyed due to configuration changes.

Of course, Google’s code implementation can also handle Fragment nesting very well. The following example shows the ViewModel store under Fragment nesting.

In the figure above, we add Fragments A, B, and C to the Activity. Fragment D, E, and F are nested in Fragment C.

Combining the knowledge explained in this article, we can get the following structure:

From the above figure, we can see that the ViewModel is always stored in a linear structure in the case of nested fragments. This structure allows the host Activity to manage all viewModels in a unified manner.

Principle that viewModels can be shared in fragments

Another feature of the ViewModel is the ability to share data in fragments. Or the legend above:

If we want Fragment D to retrieve the data in Fragment A, we can only add the ViewModel in the ViewModelStore of the Activity. Only in this way can we get the same data in different fragments. This is why when using a shared ViewModel in a Fragment, we need to pass in getActivity() when we call ViewModelProvider.of() to create the ViewModel.

Specific examples are as follows:

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected(a) {
            returnselected; }}public class FragmentA extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //👇 passes in the host Activitymodel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); }}public class FragmentD extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
             //👇 passes in the host Activity
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { item ->
               // Update the UI.}); }}Copy the code

The last

Standing on the shoulders of giants, can see further ~

  • ViewModel: Persistence, onSaveInstanceState(), UI state recovery, and Loader
  • Use the correct posture
  • Share data between fragments
  • Handling Configuration Changes with Fragments