preface

The ViewModel is one of the components in the Jetpack component library that releases the pressure on activities/fragments to manage data. Viewmodels are often used in MVVM development mode along with LiveData.

Jetpack architecture component library -Lifecycle application parsing is there

First, know the ViewModel

1. What is ViewModel?

  • ViewModel A data storage and data management component that has the host’s post-life awareness.

    • The data saved using the ViewModel persists even after the page is destroyed and rebuilt due to configuration changes (such as screen rotation);

    • The ViewModel also handles communication between activities/fragments and the rest of the application (for example, calling business logic classes);

    • The purpose of a ViewModel is to retrieve and retain information needed for an Activity or Fragment. An Activity or Fragment should be able to observe changes in the ViewModel;

    • Viewmodels typically expose this information through LiveData or DataBinding;

    • The ViewModel’s sole responsibility is to manage the UI’s data. It should never access your view hierarchy or keep references to activities or fragments.

2. Advantages of ViewModel

Page configuration change data is not lost

  • When the device is rebuilt due to configuration changes of the Activity/Fragment, the data in the ViewModel will not be lost. With LiveData, the latest saved data can be received immediately after the page is rebuilt to re-render the page.

Note: when the device is changed due to configuration, such as entering the background, screen flipping, language switching, etc

The Android Activity lifecycle has many states, and a single Activity may cycle through these different states multiple times due to configuration changes. Refer to the Activity lifecycle diagram below:

When an Activity goes through all of these states, you may also need to store transient UI data in memory. I define transient UI data as the data required by the UI, including data entered by the user, generated at run time, or loaded from the database. This data can be image bitmaps, lists of objects for RecyclerView, and so on.

For example, when the user rotates the screen, we previously saved or restored this data during configuration changes with onSaveInstanceState() and onRestoreInstanceState().

The user rotates the screen as follows: When the system starts to stop your Activity:

  • (1) It calls onSaveInstanceState() so that you can specify additional state data to save in case the Activity has to recreate an instance.

  • (2) If the Activity is broken and the same instance must be recreated, the system passes the state data defined in (1) to the onCreate() and onRestoreInstanceState() methods.

But what if your data doesn’t need to know or manage the life cycle state of your Activity, or it’s not stored in your Activity?That’s the purpose of the ViewModel class.

  • What’s the difference between ViewModel and onSaveIntanceState?
    • OnSaveIntanceState stores only lightweight key-value key-value pair data. The onSaveIntanceState is triggered only when a page is reclaimed due to a non-configuration change, and the data is stored in an ActivityRecord.
    • The ViewModel can store any Object data, only valid when the page is reclaimed due to configuration changes. It is stored in ActivityThread#ActivityClientRecord.

Life cycle induction

  • The onCleared() method can be copied to terminate the clearing operation and free up memory. This method is called when the host onDestroy.

The diagram below:

You can see the Activity’s lifecycle state flow through the rotation process until finish. The ViewModel life cycle is displayed next to the life cycle of the associated Activity. Note that this ViewModels can be easily combined with Activities/Fragments.

You typically request the ViewModel the first time the system calls the Activity object’s onCreate() method. The system may call onCreate() several times throughout the Activity’s life cycle, such as while rotating the device screen. The ViewModel exists from the time you first request the ViewModel until the Activity completes and is destroyed.

Data sharing

  • For pages with a single Fragment of Activity, you can use the ViewModel to share data between pages. In fact, different activities can also share data.

It is common for two or more fragments in an Activity to need to communicate with each other.

Imagine a common case of a master-slave Fragment. Suppose you have a Fragment in which the user selects an item from a list, and another Fragment that displays the contents of the selected item.

This situation is not easy to handle because both fragments need to define some kind of interface description, and the owner Activity must bind the two together.

In addition, both fragments must deal with situations where the other Fragment has not yet been created or is not visible.

You can use ViewModel objects to solve this common difficulty. The two fragments can handle such communication using their Activity scope shared ViewModel, as shown in the following sample code:

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 onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); / /!!!!!! Note that both fragments retrieve the Activity model that contains them = new ViewModelProvider(requireActivity()).get(sharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends Fragment { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); / /!!!!!! Please note: Both fragments retrieve the Activity SharedViewModel Model = new that contains them ViewModelProvider(requireActivity()).get(SharedViewModel.class); model.getSelected().observe(getViewLifecycleOwner(), { item -> // Update the UI. }); }}Copy the code
  • Advantages of sharing data between fragments via ViewModel:

    • 1. The Activity does not need to perform any action, nor does it need to know anything about the communication.
    • 2. Fragment does not need to know each other except for the SharedViewModel convention. If one Fragment disappears, the other Fragment continues to work as usual.
    • 3. Each Fragment has its own life cycle and is not affected by the life cycle of another Fragment. If one Fragment replaces another, the interface will continue to work without any problems.

Second, the use of ViewModel

There are three steps to using the ViewModel:

  1. Separate your ViewModel from your UI controller (Activity or Fragment) by creating a class that inherits from the ViewModel
  2. Establish communication between your UI controller and ViewModel
  3. Use the ViewModel in your UI controller

Lifecycle will be implemented with LiveData, ViewModel components and dependencies will need to be added before using the ViewModel:

Declare dependencies

Dependencies {def lifecycle_version = "implementation" "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" }Copy the code

Create a ViewModel class

Quote official: a very simple example

  • In general, you need to create a ViewModel class for each page in your app.
  • The ViewModel class controls and manages all of the page-related data and provides get/set methods for accessing the data.
  • This separates the UI code that displays the Activity from the data that you use to display the UI (the data that displays the UI is now in the ViewModel).
public class ScoreViewModel extends ViewModel {
   // Tracks the score for Team A
   public int scoreTeamA = 0;

   // Tracks the score for Team B
   public int scoreTeamB = 0;
}
Copy the code

2. Associate UI controller with ViewModel

Your UI controller (Activity or Fragment) needs to know about your ViewModel. Your UI controller can display and update data as UI interactions occur

However, ViewModels should not keep references to activities, fragments, or contexts. In addition, ViewModels should not contain elements that have references to the UI controller, such as Views, because this creates indirect references to the Context.

The reason you shouldn’t save these objects is that the ViewModels have a lifespan beyond that particular UI control instance — if you rotate your Activity three times, you’ve just created three different Activity instances, but only one ViewModel instance.

With that in mind, let’s create this UI controller /ViewModel association. You want to create a member variable for the ViewModel in your UI controller. So in onCreate, you should say:

ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
Copy the code

The ViewModelProviders class is deprecated using ViewModel 2.2.0.

Refer to the Lifecycle update documentation:

So in onCreate, you should say:

 mViewModel = new ViewModelProvider(<Your UI controller>).get(<Your ViewModel>.class)
Copy the code
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
   // Other setup code below...
}
Copy the code

Note: There is one exception to the “No context in ViewModel” rule:

Sometimes you might need an Application Context(rather than an Activity Context) to use with things like system services.

Then, you can store the application context in the ViewModel because the application context is associated with the application life cycle.

This is different from the Activity Context associated with the Activity lifecycle. In fact, if you need an Application Context, you should extend the AndroidViewModel, which is just a ViewModel containing an Application reference.

3. Use ViewModel in UI Controller

To access or change UI data, you can now use the data in the ViewModel. Here is an example of A new onCreate method and an update method that adds points to team A:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
   
   displayForTeamA(mViewModel.scoreTeamA);
   displayForTeamB(mViewModel.scoreTeamB);
}

// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
   mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
   displayForTeamA(mViewModel.scoreTeamA);
}
Copy the code

The ViewModel also works well with other architectural components. For example: LiveData,DataBinding, etc.

Three, the realization principle of ViewModel reuse

As mentioned above, the ViewModel can be reused after a page is destroyed and rebuilt due to configuration changes.

To be more precise, the same ViewModel instance object is retrieved before and after the page is restored, so that the page can be reused after the page is restored.

Get the ViewModel instance with the ViewModelProvider:

mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class); // Specify factory mViewModel = new ViewModelProvider(this, factory).get(scoreViewModel.class);Copy the code

The ViewModelProvider essentially gets the instance from the ViewModelStore that’s passed in. If not, a new one is created using factory and stored in the ViewModelStore.

1, ViewModelProvider to obtain ViewModel instance get() method source analysis

public class ViewModelProvider { private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"; private final Factory mFactory; private final ViewModelStore mViewModelStore; // The ViewModelProvider essentially gets the instance from the ViewModelStore passed in. // If not, create a new one using factory and store it in ViewModelStore. public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } owner.getViewModelStore()? public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } @NonNull @MainThread 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"); } // build a default Key from the passed modelClass return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } @nonnull @mainThread // Public <T extends ViewModel> T get(@nonnull String Key, @nonnull Class<T> modelClass) {//4, get VM instance in ViewModelStore //ViewModelStore is a collection of real ViewModel instances. HashMap<String,ViewModel> ViewModel ViewModel = mViewModelStore.get(key); 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 (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass); } else { viewModel = (mFactory).create(modelClass); } mViewModelStore.put(key, viewModel); return (T) viewModel; }}Copy the code

Owner.getviewmodelstore (); owner.getViewModelStore()

public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner { static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; } public ViewModelStore getViewModelStore() { if (getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } the if (mViewModelStore = = null) {/ / 1, the first from ` NonConfigurationInstances ` to obtain ` ViewModelStore ` NonConfigurationInstances nc instance objects = (NonConfigurationInstances) getLastNonConfigurationInstance(); // If it is not empty, it is the same ViewModel instance object, so that the page can be reused after reconstruction. if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances / / 3, ` ViewModelStore ` when it is stored in the ` NonConfigurationInstances ` inside? mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }}Copy the code

3, onRetainNonConfigurationInstance source code

Because of system reason when the page is recycled, will trigger onRetainNonConfigurationInstance method, so viewModelStore object is stored in the NonConfigurationInstance at this time. When the page is restored and rebuilt, the NonConfigurationInstance object is again passed to the new Activity for object reuse.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner { /** * Retain all appropriate non-config state. You can NOT * override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to * retain your own non config state. */ @Override @Nullable public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { // No one called getViewModelStore(), So see if there was an existing ViewModelStore from our last // Put it out NonConfigurationInstance NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; / / and return in 1, put viewModelStore NonConfigurationInstances nci. ViewModelStore = viewModelStore; // ViewModelStore is saved when the page is destroyed. return nci; }}Copy the code