preface

Today I want to talk to you about the ViewModel of Jetpack component. I know a little bit about ViewModel before. I just read about the principle of ViewModel from the blog, but I don’t know much about it. I believe you certainly no problem, everyone can understand!

So what’s a ViewModel?

The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation.

The architecture component provides the ViewModel helper class for the interface controller, which is responsible for preparing the data for the interface. ViewModel objects are automatically retained during configuration changes so that the data they store is immediately available for use by the next activity or Fragment instance.

Developer.android.com/topic/libra…

These two descriptions, taken from the official documentation, explain the ViewModel’s purpose, which is primarily to hold UI data. Let’s look at its life cycle:

As you can see, the ViewModel remains in the same state for the entire lifecycle of the UI, and onCleard() is executed when the UI is destroyed to clean up the data.

use

Next, let’s look at a simple use of the ViewModel

First we create a ViewModel

class MainViewModel : ViewModel() {
    val liveData = MutableLiveData<String>("This is LiveData data.")}Copy the code

Then get the MainViewModel in the UI and subscribe to the data

class MainActivity3 : AppCompatActivity() {
    private lateinit var viewModel: MainViewModel
    private lateinit var binding:MainFragmentBinding
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_fragment)
      
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        viewModel.liveData.observe(this){
            binding.message.text = it
        }
    }
}
Copy the code

The ViewModelProvider is instantiated first, and the MainViewModel instance object is obtained by the ViewModelProvider::get method.

So how does ViewModle store data? Today from shallow to deep take you to appreciate the secrets of the ViewModel, to ensure that everyone can understand!

The principle of

Let’s start with the ViewModelProvider and take a look at what it looks like.

    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))

Copy the code

In this case, the constructor creates a new default factory and then calls its own default constructor. This factory is used to create the ViewModel.

Now what does the get method do?

    @MainThread
    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
        valcanonicalName = modelClass.canonicalName ? :throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }
Copy the code

Here we add a default Key, and then we call another get method, and the default Key is

internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
Copy the code
    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        var viewModel = store[key]
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)? .onRequery(viewModel)return viewModel as T
        } else{... } viewModel =if (factory is KeyedFactory) {
            factory.create(key, modelClass)
        } else {
            factory.create(modelClass)
        }
        store.put(key, viewModel)
        return viewModel
    }
Copy the code

In this method, you can see that the ViewModel object is fetched from the Store based on the key value, and if the type is correct, the current object is returned immediately. If not, a new ViewModel object is created through the factory, stored in the Store and returned.

So let’s look at how does the factory create the VIewModel

    public open class NewInstanceFactory : Factory {      
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return try {
                modelClass.newInstance()
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        }
    }
Copy the code

This is the default factory created earlier by the ViewModelProvider, which finally creates the instantiation object of the ViewModel through modelClass.newinstance ().

So let’s see what store is.

The type of store is ViewModelStore

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
  
    public final void clear() {
        for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code

You can see that it maintains a HashMap that stores the ViewModel based on Key values. The clear() method is also provided to clear all viewModels.

When was this ViewModelStore created?

When creating the ViewModelProvider mentioned above, you can see that The ViewModelStoreOwner is passed in by the Activity when it creates the ViewModelProvider, and then calls the getViewModelStore() method in owner to get the ViewModelStore, And pass it into the constructor.

    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))
Copy the code

This is because AppCompatActivity’s parent ComponentActivity implements the ViewModelStoreOwner interface.

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

The ViewModelStoreOwner interface is simple, providing only a getViewModelStore() method to get the ViewModelStore. Let’s look at its implementation.

    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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }
Copy the code

This simply returns the mViewModelStore object, but the important thing is the ensureViewModelStore () method.

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
    
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
Copy the code

Here is mainly to obtain NonConfigurationInstances object, then get to viewModelStore, if NonConfigurationInstances is empty, then create a new viewModelStore object.

Next we mainly look at getLastNonConfigurationInstance (); Methods.

    public Object getLastNonConfigurationInstance(a) {
        returnmLastNonConfigurationInstances ! =null
                ? mLastNonConfigurationInstances.activity : null;
    }
Copy the code

I have translated the comments for this method:

Retrieval by onRetainNonConfigurationInstance returns before the configuration instance data. Available from the initial {@link #onCreate} and {@link #onStart} calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.

In simple terms, this method is used to obtain onRetainNonConfigurationInstance () method of storage contents.

    public Object onRetainNonConfigurationInstance(a) {
        return null;
    }
Copy the code

Called by the system as part of a destruction activity due to a configuration change, when it is known that a new instance will be created for the new configuration immediately. You can return any object you like here, including the activity instance itself, later can call getLastNonConfigurationInstance () the new activity instance to retrieve the object.

In this comparison, it is not difficult to see:

OnRetainNonConfigurationInstance () is at the time of Activity destroyed to store information.

GetLastNonConfigurationInstance () is used for storing information.

When the screen rotation occurs, will first calls onRetainNonConfigurationInstance data to save first, and then through the getLastNonConfigurationInstance saved data is available.

Let’s take a look at ComponentActivity of onRetainNonConfigurationInstance implementation:

    public final Object onRetainNonConfigurationInstance(a) {
        Object custom = onRetainCustomNonConfigurationInstance();
        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {          
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) { viewModelStore = nc.viewModelStore; }}if (viewModelStore == null && custom == null) {
            return null;
        }

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

MViewModelStore is in ensureViewModelStore approach for object (possibly through getLastNonConfigurationInstance (), may be to create). If this mViewModelStore is empty, will try to get from NonConfigurationInstances, if still is empty, returns null directly, if not empty, recreate the nci for storage.

So when will the data be cleared?

In the parameterless construction of ComponentActivity, a lifecycle listen is performed when the page is being destroyed and no configuration changes have been made

The clear() method of mViewModelStore is used to free data.

    public ComponentActivity(a) { Lifecycle lifecycle = getLifecycle(); . getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); . }Copy the code

The whole process, that’s about it

conclusion

With all this analysis, let’s get this straight:

  • Our Activity parent ComponentActivity implements the ViewModelStoreOwner interface, using the ViewModelProvider to create a viewModel using a default factory, identified by a unique Key value, It’s stored in the ViewModelStore. The next time you need it, you can get it with a unique Key.

  • Since ComponentActivity implements the ViewModelStoreOwner interface, it implements the getViewModelStore method, which is called first when the screen rotates

ViewModelStore onRetainNonConfigurationInstance () method can save up and then call getLastNonConfigurationInstance method to data recovery, if is empty, Will recreate viewModelStore and stored in the global, when there is a change to the following time, can pass onRetainNonConfigurationInstance saved.

  • Finally, when the page is destroyed and no configuration changes are made, theviewModelStoreTo clear the data in.

Write in the last

So much for today, I hope this article has been helpful to you on your introspective journey. Some time ago, I heard a saying, “The best time to plant a tree is ten years ago and now”.