Android Architecture Components ViewModel Component Parsing

Keywords: Fragment Android Google

1 Preface

ViewModel is a very important component of Android architecture component. It is the core of Android architecture hierarchy. You can refer to its usage and information.
Android Architecture Components: An Introduction to Android Architecture Components Development
You can also refer to official examples. https://developer.android.google.cn/topic/libraries/architecture/viewmodel.html
Next, we introduce the ViewModel component in the following aspects

2 Role of ViewModel

The official definition of the ViewModel component is as follows:
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

ViewModel is a component that stores and manages data recreated by lifecycle, which is still in the lifecycle when the configuration changes or the screen rotates. In addition, ViewModel can also be used to handle communication between UI components, which is the key to decoupling the Activity/Fragment View layer.

ViewModel is located in the Android architecture component as follows:

You can see that the ViewModel lies between the Activity(View) and Repository(Model) layers. It is similar to the P layer in MVP. It provides an interface to operate the Model layer downwards, observes the Model layer upwards using the observer mode, and notifies the View layer when the data of interest in the Model layer changes. Of course, this is implemented by the LiveData component referred to in the ViewModel. This will be done in the next blog post. Let's talk about it.
One of the great implications of the ViewModel component is that its instance does not recreate the ViewModel object because of configuration changes or screen rotation. This is one of the basic features that distinguish ViewModel from P in MVP. The following illustrations are given to illustrate the lifecycle of ViewModel.

As you can see, only when the finish() method of Activity is called, the ViewModel.onCleared() method is called, and the object is destroyed. This diagram is a good description of the lifecycle of ViewModel when Activity is recreate d. Refer to the following instructions on how to do this.

Note: * Do not hold a reference to the View layer such as Activity in the ViewModel. If you need to refer to Context, use the Android ViewModel, which refers to Application Context *
The official statement is as follows:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context

3 ViewModel UML Class Diagram

Looking at the source code of the Android architecture component, we can get the following UML class diagram.

Here are some of the more important classes
ViewModel Providers: Create a tool class for ViewModel Provider, providing the following four methods to create ViewModel Provider

public static ViewModelProvider of(@NonNull Fragment fragment)
public static ViewModelProvider of(@NonNull FragmentActivity activity)
public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory)
public static ViewModelProvider of(@NonNull FragmentActivity activity,@NonNull Factory factory)

The Factory created can be specified at the time of creation. By default, there is already a Factory, which is defined as follows:

public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {

        private Application mApplication;

        /**
         * Creates a {@code DefaultFactory}
         *
         * @param application an application to pass in {@link AndroidViewModel}
         */
        public DefaultFactory(@NonNull Application application) {
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }

Rewrite public < T extends ViewModel > T create (@NonNull Class < T > Model Class) to create ViewModel objects.

ViewModelProvider: The class that actually creates and stores the ViewModel is saved in the ViewModelStore object mViewModelStore
The creation logic is as follows:

@NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

ViewModelStores: Create a tool class for ViewModelStore, associate with HoldFragment, and return the ViewModelStore object in HolderFragment
ViewModel Store: Save and manage the class of ViewModel, which refers to a HashMap to save the ViewModel object. And when HolderFragment is destroyed, the saved ViewModel object is cleaned up.

public class ViewModelStore {

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

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

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

Holder Fragment: Inherited from Fragment, is the secret of ViewModel in configuration changes that are not destroyed. It mainly provides the ViewModel Store needed for ViewModel Provider to create ViewModel.
There are the following key codes in ViewModel Stores:

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }

Here holder FragmentForis the method in Holder Fragment, which is defined as follows:

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

    ....
    HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

Refer to the source code for a detailed implementation of these classes.

4 ViewModel parsing

1 ViewModel instance creation process
The process of creating an instance of ViewModel is rather complicated. First, through the ViewModel Providers class, then through the ViewModel Stores, the ViewModel Store object is obtained by jumping to HolderFragment, and finally to the public < T extends ViewModel > T get (@NonNull String key, @NonNull Class < T > Model Class) method of ViewModel Provider. The whole process is as follows:

So, here we finally look at the public < T extends ViewModel > T get (@NonNull String key, @NonNull Class < T > Model Class) method of ViewModel Provider.

@NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

Major things have been done as follows
(1) Obtain the ViewModel object from the mViewModel Store, which is a ViewModel Store object. When creating the ViewModel Provider, it must be NULL for the first time.
(2) Judging whether it is an instance object of ViewModel, if it is returned directly, when the acquired object is not NULL and the delivered Class object is correct, it can be returned directly here, indicating that the VikewModel object was created before?
(3) Call mFactory's < T extends ViewModel > T create (@NonNull Class < T > Model Class) to create a ViewModel object, which is specified by the Creating ViewModel Provider and is generally created by reflection. For example, the default DefaultFactory object.
(4) After creating the ViewModel object, the object is cached, saved in the mViewModel Store and returned to the ViewModel object.

In this way, the whole process of creating the ViewModel is over. Or borrow someone else's sequence diagram.

Why won't the 2 ViewModel instance be destroyed because Activity is recreated?
It's an interesting question. I was curious when I first came into contact with the official description of ViewModel. How did this work? We know that when an Activity/Fragment changes its configuration due to screen rotation, the referenced objects are rebuilt, typically dialog boxes, so we have to save some data in Activity's onSave Instance State () and restore the data in onCreate(). But ViewModel doesn't need it, so how does ViewModel do it? We look at the source code of ViewModel Providers and find that each of the () methods creates a new ViewModel Provider object, which is even more confusing.

    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

The answer is two key points:
(1) The life cycle of Holder Fragment being re-create d in Activity/Fragment
(2) mViewModelStore object in ViewModel Provider
These two key points are illustrated in turn here.

(1) The life cycle of Holder Fragment being re-create d in Activity/Fragment
Reading the source code of HolderFragment carefully, we find that there is such a sentence in the construction method.

    public HolderFragment() {
        setRetainInstance(true);
    }

Very insignificant, but very important.
Set Retain Instance (true); indicates that Fragment will not reside in the system when the screen rotation is destroyed due to configuration changes, until Activity/Fragment is recreated, and then continues to be used on onAttach, that is to say, Holder Fragment will not walk onDestory() - > onCreate () when the screen rotation is destroyed due to configuration changes. Method. This ensures that the HolderFragment object is the same when the Activity / Fragment is recreated, and that the mViewModelStore in it will not change.

(2) mViewModelStore object in ViewModel Provider
Holder Fragments will not be recreated when Activity/Fragment is recreated from the previous analysis, so the ViewModel Store returned from the Natural ViewModel Stores is still the previous ViewModel Store.

    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }

This results in the creation of the ViewModel Store object passed in by the ViewModel Provider or the ViewModel Store object before Activity/Fragment

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

So even if there are more instances of the new ViewModelProvider, the mViewModelStore object in it has not been recreated. This will be returned in step (2) when the get (@NonNull String key, @NonNull Class < T > ModelClass) method is called, and the returned ViewModel object is the ViewModel object before the Activity/Fragment recreate.

(1) (2) ensures that the ViewModel instance will not be destroyed because Activity is recreated.

Of course, after Activity finish, HoldFragment calls the onDestory() method, which cleans up the ViewModel in the mViewModelStore.

So far, the analysis of ViewModel components has been basically completed. The next article will analyze LiveData components in Android architecture components.

Reference resources:
https://developer.android.google.cn/topic/libraries/architecture/viewmodel.html
https://shymanzhu.com/2017/12/28/Android%E6%9E%B6%E6%9E%84%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%89%EF%BC%89%E2%80%94%E2%80%94ViewModel/

Posted by Skunkmaster on Sun, 16 Dec 2018 02:15:04 -0800