space.bilibili.com/474380680

An overview of the

ViewModel, literally, it has to do with views and data models. It is responsible for preparing and managing data classes related to UI components (fragments/activities), that is, the ViewModel is used to manage uI-related data, and it can also be used to manage communication between UI components.

Problems that existed before

The ViewModel is used to store and manage UI-related data. It can be used to abstract the data logic associated with an Activity or Fragment component, and can be adapted to the life cycle of the component. For example, the data in the ViewModel is still valid after the Activity is rebuilt after the screen is rotated.

Before introducing the ViewModel, there were several problems:

Normally the Android system manages the life cycle of UI Controllers (such as activities and fragments) by responding to user interactions or recreating components that the user cannot control. After the component is destroyed and rebuilt, the data related to the original component will also be lost. If the data type is simple and the amount of data is small, you can use onSaveInstanceState() to store the data. After the component is rebuilt, you can use onCreate() to read the Bundle data to restore the data. However, if a large amount of data is not easy to serialize and deserialize, the above method will not be applicable. The UI Controllers often send many asynchronous requests, and it is possible that the UI component has been destroyed and the request has not been returned, so the UI Controllers need to do extra work to prevent memory leaks. When an Activity is destroyed and rebuilt due to a configuration change, the data will normally be rerequested, which is a waste of time, and it is better to retain the data from the last time. UI Controllers are simply responsible for displaying UI data, responding to user interactions, and system interactions. However, developers often write a lot of data requests and processing work in activities or fragments, resulting in bloated UI Controllers class code and making unit testing difficult. We should follow the separation of responsibilities and separate data related matters from THE UI Controllers.

ViewModel basic use

public class MyViewModel extends ViewModel { private MutableLiveData<List<User>> users; public LiveData<List<User>> getUsers() { if (users == null) { users = new MutableLiveData<List<Users>>(); loadUsers(); } return users; } private void loadUsers() {private void loadUsers() {Copy the code

The new Activity is as follows:

public class MyActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class); Model.getusers ().observe(this, users -> {// update UI}); }}Copy the code

If the Activity is recreated, it receives the same MyViewModel instance created by the previous Activity. When the owning Activity terminates, the framework calls the onCleared() method of the ViewModel to clear the resource.

Because a ViewModel lives within the specified Activity or Fragment exception, it should never reference a View, or hold any classes that contain references to the Activity context. If the ViewModel needs an Application context (such as getting system services), you can extend the AndroidViewmodel and have a constructor to receive the Application.

Share data between fragments

It is common for multiple fragments in an Activity to communicate with each other. Previously, each Fragment needs to define the interface description, and the Activity that it belongs to binds the two together. In addition, each Fragment must handle situations where other fragments are not created or not visible. This can be resolved by using the ViewModel. These fragments can use their activities to share the ViewModel to handle communication:

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 onActivityCreated() { model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends LifecycleFragment { public void onActivityCreated() { SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); model.getSelected().observe(this, { item -> // update UI }); }}Copy the code

GetActivity () returns the same host Activity, so both fragments return the same SharedViewModel object.

SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
Copy the code

The benefits of this approach include:

2. Fragments do not need to know about each other, except for SharedViewModel. If one of them fragments disappears, the rest can still work as usual. 3. Each Fragment has its own life cycle and is not affected by the life cycle of other fragments. In fact, one Fragment replaces another, and the UI doesn’t work at all.

ViewModel lifecycle

The scope of the ViewModel object is determined by the Lifecycle passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel remains in memory until Lifecycle leaves permanently — for an Activity when it finishes, for a Fragment when it detached.

On the left side of the image above is the Activity’s life cycle, with a rotation of the screen. On the right is the lifecycle of the ViewModel.

The ViewModel is typically initialized with code like this:

viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
Copy the code

The this parameter is usually an Activity or Fragment, so the ViewModelProvider can get the life cycle of the component.

The Activity may fire onCreate() several times during its life cycle, and the ViewModel is created only on the first onCreate() before the Activity is destroyed at the end.

ViewModel Related class diagram

ViewModelProviders is a ViewModel utility class that provides methods for accessing viewModels via fragments and activities, implemented by the ViewModelProvider.

ViewModelProvider is a utility class that implements ViewModel creation and retrieval. The ViewModelProvider defines an interface class — Factory — that creates viewModels. The ViewModelProvider has a ViewModelStore object that stores ViewModel objects.

ViewModelStore is a class that stores viewModels, implemented using HashMap to store ViewModle objects.

ViewModel is an abstract class that defines only the onCleared() method, which is called when ViewModel is not in use. ViewModel has a subclass AndroidViewModel. This class is handy for using Context objects in ViewModel, because we mentioned earlier that you can’t hold a reference to an Activity in a ViewModel.

ViewModelStores is a ViewModelStore factory method class that is associated with HolderFragment, which has a nested class called HolderFragmentManager.

ViewModel Related sequence diagram

Tracing back to the source code to create a ViewModel, you’ll notice that there are a few steps required. Let’s take a look at the sequence diagram of getting a ViewModel object in a Fragment.

The sequence diagram looks complicated, but it only describes two processes:

I get the ViewModel object. When the HolderFragment is destroyed, the ViewModel receives an onCleared() notification.

ViewModel related source code analysis

The ViewModelProviders class is implemented as follows:

public class ViewModelProviders {

    private static Application checkApplication(Activity activity) {
        Application application = activity.getApplication();
        if (application == null) {
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }

    private static Activity checkActivity(Fragment fragment) {
        Activity activity = fragment.getActivity();
        if (activity == null) {
            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
        }
        return activity;
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(checkActivity(fragment)));
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
        checkApplication(checkActivity(fragment));
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

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

ViewModelProviders provide four of() methods that have similar functions. Of (FragmentActivity Activity, Factory Factory) and of(Fragment Fragment, Factory Factory) provide custom methods for creating viewModels.

  1. Check whether the Fragment is Attached to the Activity and whether the Application object of the Activity is empty.
  2. Creating a ViewModel object seems simple, one line of code.
new ViewModelProvider(ViewModelStores.of(fragment), factory)
Copy the code

Let’s look at the ViewModelStores.of() method:

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }
Copy the code

Digging deeper, we realized an interface:

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}
Copy the code

HolderFragmentFor () is a static method of HolderFragment, which inherits from the Fragment. Let’s first look at the implementation of the holderFragment() method

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

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
Copy the code

Continue to see HolderFragmentManager. HolderFragmentFor () method of concrete realization

HolderFragment holderFragmentFor(Fragment parentFragment) { FragmentManager fm = parentFragment.getChildFragmentManager(); HolderFragment holder = findHolderFragment(fm); if (holder ! = null) { return holder; } holder = mNotCommittedFragmentHolders.get(parentFragment); if (holder ! = null) { return holder; } parentFragment.getFragmentManager() .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false); holder = createHolderFragment(fm); mNotCommittedFragmentHolders.put(parentFragment, holder); return holder; } private FragmentLifecycleCallbacks mParentDestroyedCallback = new FragmentLifecycleCallbacks() { @Override public void  onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) { super.onFragmentDestroyed(fm, parentFragment); HolderFragment fragment = mNotCommittedFragmentHolders.remove( parentFragment); if (fragment ! = null) { Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment); }}}; private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { HolderFragment holder = new HolderFragment(); / / create HolderFragment object fragmentManager beginTransaction (). The add (holder, HOLDER_TAG). CommitAllowingStateLoss (); return holder; } public HolderFragment() {// This is the key, which makes the Fragment onDestroy() and onCreate() not called setRetainInstance(true) when the Activity is set;  }Copy the code

SetRetainInstance (Boolean) is a method in the Fragment. Setting this method to true will enable the current Fragment to survive the Activity rebuilding. If this method is not set or set to false, the current Fragment will also rebuild during the Activity rebuilding and be replaced by a new object.

Put a Map of the ViewModel in the Fragment whose setRetainInstance(Boolean) is true. Naturally all viewModels in the Fragment will be saved from Activity rebuilding. Add a ViewModel component to the Fragment Map by binding both activities and fragments to the Fragment Map.

So at this point, we’ve got our ViewStore object, New ViewModelProvider(ViewModelStores. Of (fragment)) SDefaultFactory) now look at the constructor of the ViewModelProvider

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

Now you can get the ViewModel object using the viewModelProvider.get () method. Let’s look at the implementation of this method

@NonNull @MainThread public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); // See if there are any existing ViewModel objects in the cache. if (modelClass.isInstance(viewModel)) { //noinspection unchecked return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel ! = null) { // TODO: log a warning. } } viewModel = mFactory.create(modelClass); // Create the ViewModel object and cache it. mViewModelStore.put(key, viewModel); //noinspection unchecked return (T) viewModel; }Copy the code

The viewModelProvider.get () method is simpler, as noted in the comments. Finally, let’s look at the concrete implementation of the ViewModelStore class

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

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

ViewModelStore is a class that caches viewModels. Put () and get() methods are used to access ViewModel objects, and clear() methods are provided to clear the cached ViewModel objects. In this method, the viewModel.oncleared () method is called to notify the ViewModel object that it is no longer in use.

ViewModel receives an onCleared() notification

HolderFragment’s onDestroy() method

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }
Copy the code

The viewModelStore.clear () method is called in the onDestroy() method, and we know that the onCleared() method of the ViewModel is called in this method. After you look at the Source code of HolderFragment, you may have a question: where is the ViewModel object saved by mViewModelStore added? If you look carefully, you’ll see that in the constructor of the ViewModelProvider, Has passed a reference to the mViewModelStore ViwModelStore object in The HolderFragment to the mViewModelStore object in the ViewModelProvider, The viewModelProvider.get () method adds a ViewModel object to the mViewModelStore.

conclusion

The ViewModel is responsible for managing and requesting data for activities or fragments. The specific data request logic should not be written in the ViewModel, otherwise the responsibilities of the ViewModel will become too heavy. For details, see Android Architecture Components. Viewmodels can be used to interact with different fragments within an Activity or as a way of decoupling them. The ViewModel can also handle some activities/fragments interacting with other modules in the application. The ViewModel life cycle (in the case of an Activity) starts when the Activity first onCreate() and ends when the Activity finally finishes.