Insight into the ViewModel of architecture components

Official document connection (wall climbing required)

1, Introduction

The ViewModel class is designed to store and manage UI related data in a life-cycle aware manner. The data in the ViewModel will survive even if the activity configuration changes, such as when the horizontal and vertical screens are switched.

If the above is the official website, it won't be translated. It's meaningless and English is not good. Let's talk in my vernacular.
Let's first look at the pain points that ViewModel can solve.

1. Data persistence

We know that when the screen rotates, it will experience the destruction and re creation of activities. This involves the problem of data saving. Obviously, re requesting or loading data is unfriendly. Before the emergence of ViewModel, we can use the onSaveInstanceState() mechanism of activity to save and recover data, but the disadvantage is obvious. onSaveInstanceState is only suitable for saving a small amount of data that can be serialized and deserialized. If we need to save a large bitmap list, this mechanism is obviously inappropriate.
Due to the special design of ViewModel, this pain point can be solved.
Let's take a look at the ViewModel life cycle diagram:



As can be seen from the figure, the ViewModel life cycle runs through the entire activity life cycle, including the re creation of the activity due to rotation, which will not end until the activity is actually destroyed. In that case, it's better to store data.

2. Asynchronous callback problem

Usually, our app needs to frequently request data asynchronously, such as calling the interface to request server data. Of course, the callbacks of these requests are quite time-consuming. Previously, we received these callbacks in Activity or fragment. Therefore, we have to consider the potential memory leakage. For example, the interface request is returned only after the Activity is destroyed. Dealing with these problems will add a lot of complex work to us.
But now we use ViewModel to process data callback, which can perfectly solve this pain point.

3. Share the burden of UI controller

From the earliest MVC to the current popular MVP and MVVM, the purpose is to clarify responsibilities and separate the burden of UI controller.
UI controller s such as Activity and Fragment are designed to render and display data, respond to user behavior, and process some interactions of the system. If you ask him to be responsible for loading network or database data, it will make it look bloated and difficult to manage. Therefore, in order to be concise, refreshing and smooth, we can separate the responsibility of data operation from ViewModel.

4. Sharing data between Fragments

(you can look at the following usage introduction first)

For example, there are multiple fragments in an Activity, and some interaction needs to be done between the fragments. My previous practice is that interface callbacks need to be managed uniformly in the Activity, and inevitably fragments have to hold each other's references. If you think about it carefully, you can see that this is a very important thing. If the coupling is high, you still need a lot of fault-tolerant judgment (such as whether the other party's fragment is still alive).

So what about using ViewModel (example on the official website):
(an activity and its internal fragment s can share a ViewModel)

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() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

If you carefully experience such benefits, you will find:
1. Activity doesn't need to do anything, and doesn't even know about this interaction. It is perfectly decoupled.
2. The Fragment only needs to interact with the ViewModel, and does not need to know the status or even existence of the other Fragment, let alone hold its reference. When the other party's Fragment is destroyed, it will not affect any work of itself.
3. The life cycle of fragments does not affect each other, and even the replacement of fragments with others does not affect the operation of the system.

2, Usage introduction

ViewModel is generally used with LiveData. LiveData can refer to me Another article.
First, get the ViewModel instance through the provided class ViewModelProviders:

 MyViewModel model = ViewModelProviders.of(activity).get(MyViewModel.class);
or
 MyViewModel model = ViewModelProviders.of(fragment).get(MyViewModel.class);

Or with Factory

 MyViewModel model = ViewModelProviders.of(activity,factory).get(MyViewModel.class);

VM internal operations:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }
    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Then, you can observe the data changes in the activity:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

3, Principle of source code analysis

Start from the beginning of the ViewModel life cycle. When did it start? Nonsense, of course, starts when we instantiate it. When do we instantiate it? The original words on the official website are:
You usually request a ViewModel the first time the system calls an activity object's onCreate() method.
Yes, we usually initialize in onCreate.

ViewModel birth:

The instantiated code is very simple. Let's slowly analyze what we have done

ViewModelProviders.of(activity,factory).get(MyViewModel.class)

1. The first is the of method of ViewModelProviders:

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

If the parameter has activity and fragment, I will only post the activity. The key point is that a Factory is introduced here. The method without Factory only initializes a sDefaultFactory (Factory implementation class) through initializefactoryifneed:

    /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

There is only one create method. If you think about it with your fingers, you know it must be used to initialize viewmodel. Put it in the until you can use it. Continue to see the method:

return  new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory)

Two new classes, ViewModelProvider and ViewModelStores, appear. Look at ViewModelProvider first:

public class ViewModelProvider {
    private static final String DEFAULT_KEY =
            "android.arch.lifecycle.ViewModelProvider.DefaultKey";
    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;
    ......
    @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;
    }
}

The superfluous items are omitted. This class simply maintains an mFactory, a newly emerging class ViewModelStore (to be discussed later), and provides a get method for generating ViewModel with mFactory and ViewModelStore. Wow, here we have seen the final instantiation of ViewModel, but don't worry, there are still a lot of things.

Look again

ViewModelStores.of(activity)

What did you do. First look at ViewModelStores:

/**
 * Factory methods for {@link ViewModelStore} class.
 */
@SuppressWarnings("WeakerAccess")
public class ViewModelStores {
    private ViewModelStores() {
    }
    /**
     * Returns the {@link ViewModelStore} of the given activity.
     *
     * @param activity an activity whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }
    /**
     * Returns the {@link ViewModelStore} of the given fragment.
     *
     * @param fragment a fragment whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        return holderFragmentFor(fragment).getViewModelStore();
    }
}

There are only two of methods. holderFragmentFor is a static method that initializes HolderFragment. The trick is that HolderFragment plays a vital role. I won't talk about its important role here, just look at getViewModelStore()

public class HolderFragment extends Fragment {
......
    private ViewModelStore mViewModelStore = new ViewModelStore();
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
......
}

Nothing to say. Look at ViewModelStore:

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();
    }
}

Obviously, it is a class used to store ViewModel instances. It internally maintains a HashMap to store ViewModel,
It also provides get, put and clear methods.

What has ViewModelProviders of done so far:
1. The ViewModelProvider is initialized, and the Factory used to create the VM and the ViewModelStore where the user stores the VM are maintained internally;
2. Initialize the Factory used to generate the ViewModel (DefaultFactory by default);
3. The HolderFragment and ViewModelStore are instantiated through the static method of ViewModelStores

2. Then the get method of ViewModelProvider:

It's already posted on it. Please post it again:

ViewModelProviders.of(activity,factory).get(MyViewModel.class);
    @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;
    }

The logic is not complicated. First, see whether the ViewModelStore has been saved. If not, instantiate it through the factory and coexist in the ViewModelStore.

Death of ViewModel:

Let's look at how ViewModel ended its life.

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

Vm has only one onCleared method, so when did it call it
Here we will introduce the holder fragment mentioned earlier,

public class HolderFragment extends Fragment {
    private static final String LOG_TAG = "ViewModelStores";
    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String HOLDER_TAG =
            "android.arch.lifecycle.state.StateProviderHolderFragment";
    private ViewModelStore mViewModelStore = new ViewModelStore();
    public HolderFragment() {
        setRetainInstance(true);
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }
   @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
    static class HolderFragmentManager {
        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
        ......
         private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
            return holder;
        }
         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;
        }
  ......
}

When Vm was created, it was mentioned that a HolderFragment was instantiated. When instantiating, use the createHolderFragment method above to the fragmentmanager beginTransaction(). add(holder, HOLDER_TAG). commitAllowingStateLoss();
We know that after commit, fragment will have soul and obtain life cycle. Look at the onDestroy method
Mviewmodelstore. Was called clear();

 public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }

So far, Google has made full use of the feature of fragment life cycle to make Vm complete onCleared.
(I have to say here that Google's lifecycle and ViewModel all use some features of fragment to play these life cycles. Why do I like using fragment so much? I haven't fully understood it yet)

So the question is, why does the horizontal and vertical screen switching ViewModel not onCleared?
Look, there is one in the construction method of HolderFragment
setRetainInstance(true);
So everything is clear, (if you don't know, baidu what this method is for)
Google really makes full use of fragment features.

4, Attention

The official website uses a large red exclamation mark to indicate:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
Because the ViewModel life cycle may be long and the activity life cycle, in order to avoid memory leakage, Google prohibits holding references to Context or activity or view in the ViewModel.
This bothered me for a long time. Later, I found that there is an AndroidViewModel class, which internally maintains an ApplicationContext. It is really impossible to use one

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;
    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }
    /**
     * Return the application.
     */
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

Added by bjoerndalen on Thu, 13 Jan 2022 23:26:52 +0200