directory

  • 1 What is a ViewModel
    • 1.1 Consider two scenarios
    • 1.2 disadvantages
    • 1.3 Special Instructions
    • 1.4 Problems solved by ViewModel
  • 2. Implementation principle of ViewModel
    • 2.1 the ViewModel class
    • 2.2 Construction process of ViewModel
  • 3 ViewModel configuration independent Principle (born and died with host Controller)
    • 3.1 ViewModelStore tree
    • 3.2 System-level configuration is not supported
  • 4 ViewModel life cycle in FragmentActivity
  • 5 Multiple controllers share the ViewModel
  • 6. Some thoughts on the factory model




1 What is a ViewModel[Top]

1.1 Consider two scenarios

  • Scene 1: The APP we developed can turn to the screen, after which the reconstruction of Controller(Activity or Fragment) will be triggered. In order to maintain the consistency of data before and after the turn to the screen, we either save the data to be maintained in onSaveInstance in the form of Bundle. If necessary, we need to implement the complicated Parceable interface for composite data. If the amount of data is too large, we must persist the data, and then pull the data from database or networks.
  • Scenario 2: Our Activity maintains multiple fragments at the same time. Each Fragment needs to share some data. Traditionally, the host Activity holds the shared data and exposes the data acquisition interface to each parasitic Fragment.

1.2 disadvantages

With the expansion of business scale, the above two scenarios become more and more complicated and difficult to maintain in traditional implementation methods, and the data modules are not easy to be tested independently.

1.3 Special Instructions

With regard to scenario 1, the same scenario applies when various configuration related information changes, such as keyboard, system font, language area, etc., all of which together cause the current Controller to be rebuilt.

1.4 Problems solved by ViewModel

ViewModel is part of Android’s new MVVM framework and is designed to address the over-coupling of data and Controller in both scenarios. The basic idea is to maintain a configuration-independent object that stores any data needed in the Controller, whose life cycle is consistent with that of the host Controller, and which is not inoperative when the Controller is rebuilt (note: A Controller rebuild is still in the Controller lifecycle and does not create a new lifecycle, i.e. the Controller’s onDestroy is not called)

This means that Controller rebuilds due to configuration changes, such as screen transitions or system font changes, will not reclaim data maintained in the ViewModel, and the rebuilt Controller can still recover its state by retrieving data from the same ViewModel.




2. Implementation principle of ViewModel[Top]

2.1 the ViewModel class

If you look at the implementation of the ViewModel class, you will see that although it is an abstract class, it does not expose any externally accessible methods. The methods it reserves are package access, and it reserves some data cleaning capabilities, presumably reserved by the system for future extension. Because it’s not relevant to our understanding of the ViewModel principle, we’ll skip it.

2.2 Construction process of ViewModel

Let’s analyze the ViewModel construction process with a structure diagram:

As shown in the figure:

  • All instantiated ViewModels are cached in a wrapper object called ViewModelStore, which is essentially a HashMap;
  • The ViewModelStore is bound to the specific Controller and is born and died with the host Controller, so this explains why the ViewModel has the same lifetime as the host Controller. Because caching its ViewModelStore has the same lifetime as the host Controller;
  • The process of obtaining a ViewModel instance is delegated to a utility class called ViewModelProvider, which contains a Factory class to create the ViewModel and a reference to the ViewModelStore;
  • The overall construction process is as follows: first get the cached ViewModel from ViewModelStore; if it has not been cached, then use Facotry to instantiate a new ViewModel and cache it. The specific process is divided into four steps, please refer to the diagram for details.

The rest of this section analyzes the source code. For those who only care about the principles, this section can be skipped:

When we get the ViewModel, we usually do the following:

// Call it in the onCreate method of the Controller (for example, Fragment)
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
Copy the code

Let’s look at the implementation of viewModelproviders.of () :

public static ViewModelProvider of(@NonNull Fragment fragment) {
    return of(fragment, null);
}
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }

    // Finally instantiate one using the host Controller's ViewModelStore and a Factory
    // ViewModelProvider
    return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
Copy the code

Let’s take a look at the viewModelProvider.get () method to get a ViewModel instance:

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

    // We see the key representation of the ViewModel in the ViewModelStore
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {

    // Check whether it exists in the cache first
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if(viewModel ! =null) {
            // TODO: log a warning.}}// Not in the cache, via the Factory constructor
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }

    // The new instance is stored in the cache
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}
Copy the code




3 ViewModel configuration independent Principle (born and died with host Controller)[Top]

In the last section, we said that the ViewModel is lifecycle consistent with the host Controller because the ViewModelStore where it is stored is lifecycle consistent with the host Controller. So why does the ViewModelStore stay consistent with the Controller lifecycle?

Here we need to clear the relationship between the FragmentActivity and its host Fragment’s ViewModelStore:

3.1 ViewModelStore tree

As shown in the figure:

  • Each ViewModelStore is attached to its host Controller, so the viewModelStores of each Controller form a reference tree;
  • The ViewModelStore at the top level is attached to the FragmentActivity. In addition to holding the user-level ViewModel, it also holds the FragmentManagerViewModel of its son fragments;
  • FragmentManagerViewModel maintains two objects: A reference to the ViewModelStore of the owning Fragment and the FragmentManagerViewModel of its son Fragment. All fragments of level 2 and below share the Child FragmentManagerModel of the same parent node. When the parent Fragment is destroyed, it is easy to clear the FragmentManagerViewModel shared by all its children.
  • A Fragment’s own ViewModel changes should not affect its sibling’s ViewModel. Therefore, it can be inferred that a Fragment’s own ViewModel changes should not affect its sibling’s ViewModel. Their common FragmentManagerViewModel is supposed to maintain a container that holds a ViewModelStore for each Fragment. If you look at the FragmentManagerViewModel source code, And that’s actually what it does.

So, we see that the ViewModelStore of the top-level FragmentActivity is a super Store that references all ViewModels, including its own data, all descendant fragments’ ViewModels, All data is maintained in the ViewModelStore tree as long as each descendant Fragment does not clear its own ViewModelStore.

So how does the ViewModelStore tree stay the same when the configuration changes?

3.2 System-level configuration is not supported

To hold ViewModelStore as configuration irrelevant data, do this in the FragmentActivity:

Yes, the process is so simple, just need to ViewModelStore encapsulated in a special object to save and in FragmentActivity onRetainNonConfigurationInstance () method returns:

/**
 * Called by the system, as part of destroying an
 * activity due to a configuration change, when it is known that a new
 * instance will immediately be created for the new configuration.  You
 * can return any object you like here, including the activity instance
 * itself, which can later be retrieved by calling
 * {@link #getLastNonConfigurationInstance()} in the new activity
 * instance.
 */
@Override
@Nullable
public final Object onRetainNonConfigurationInstance(a) {
    Object custom = onRetainCustomNonConfigurationInstance();
    ViewModelStore viewModelStore = mViewModelStore;

    / /... Omit principle-independent code

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
Copy the code

This ensures, at the top level source, that all Controller ViewModels will not be destroyed due to Controller reconstruction when a configuration change is sent.

In addition, in the Fragment layer, you must distinguish between onDestroy and configuration changes. If onDestroy is the case, you must clean up your own ViewModelStore. If onDestroy is the case, you cannot clean up your own ViewModelStore.

This also shows that the ViewModel lifecycle of the Fragment is consistent with the lifecycle of the Fragment.

// FragmentManagerImpl.moveToState()
/ /... omit
booleanbeingRemoved = f.mRemoving && ! f.isInBackStack();// Whether to destroy, or false if it is a configuration change
if (beingRemoved || mNonConfig.shouldDestroy(f)) {
    boolean shouldClear;
    if (mHost instanceof ViewModelStoreOwner) {
        // This is the first layer of Fragment, as long as the top ViewModelStore does not clean the FragmentManagerViewModel
        shouldClear = mNonConfig.isCleared();
    } else if (mHost.getContext() instanceof Activity) {
        // The Fragment is a descendant of the FragmentActivity. The Fragment needs to be cleaned according to whether the configuration changedActivity activity = (Activity) mHost.getContext(); shouldClear = ! activity.isChangingConfigurations(); }else {
        shouldClear = true;
    }
    if (beingRemoved || shouldClear) {
        // Clean up only if it does destroy
        mNonConfig.clearNonConfigState(f);
    }
    f.performDestroy();
    dispatchOnFragmentDestroyed(f, false);
}
/ /... omit
Copy the code




4 ViewModel life cycle in FragmentActivity[Top]

Finally, we need to explain how the ViewModel lifecycle of the FragmentActivity is consistent with that of the FragmentActivity, . In addition to the section on FragmentActivity onRetainNonConfigurationInstance () the configuration in independent guarantee outside, still need to make sure that at the time of Activity really destroyed its holdings of ViewModel should also be clear.

The implementation of this code is very simple. All that needs to be done is to observe the Activity’s Lifecycle state and clean it up when the state is destroyed. There will be a special section on Lifecycle.

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // Observed that the Activity was destroyed
            if(! isChangingConfigurations()) {// If it is not a configuration change, clean it upgetViewModelStore().clear(); }}}});Copy the code




5 Multiple controllers share the ViewModel[Top]

As we can see from the ViewModelStore tree in Section 3.1, if multiple controllers need to share the same ViewModel, We only need to store the ViewModel in the ViewModelStore of the shared parent Controller of these controllers, and these child controllers can obtain the shared ViewModel in the following ways:

[Fragment/FragmentActivity] parentContrl = ... // The common parent Controller
final CommonViewModel viewModel = ViewModelProviders.of(parentContrl).get(CommonViewModel.class);
Copy the code

This solves the problem of sharing data in the second scenario mentioned in Section 1.1.




6. Some thoughts on the factory model[Top]

Going back to the process of getting a ViewModel instance in Section 2, we found that ViewModelProviders are actually equivalent to a simple factory pattern, while Facotry is a factory method pattern. The former can construct different ViewModelProviders based on different parameters, while the latter can implement different concrete Factories to construct different ViewModels.

There are two levels of abstraction:

  • How to implement and instantiate a data model, the implementation details of each data model are unpredictable -> ViewModel;
  • What rule to use to get a ViewModel instance is a uniform and predictable rule that doesn’t care about the details of the ViewModel implementation -> ViewModelProvider.

So we see that when an object is constructed using a uniform rule (such as ViewModelProvider), it is appropriate to use the simple factory pattern because the rule itself can be encapsulated. When there are no rules to follow for the construction of an object, the implementation details are more relevant to the business, and the part that can be encapsulated is only its new methods, then the factory method pattern is more suitable for implementation.






Description:

This document refers to the androidx version as

The core: 1.1.0

Lifecyle: 2.2.0 – alpha01

Fragments: 1.1.0 – alpha09


If you like the article I wrote, welcome to scan the code to pay attention to the public number of the small house, there will be more interesting content to share with you: