The introduction

A recent relearning of Jetpacl related components is documented here

A couple of questions

What is a ViewModel? What does it do when the screen rotates or when the configuration changes, how does the Activity save data how does the ViewModel save data

Introduce the ViewModel

The ViewModel class is designed to store and manage UI-related data in a life-cycle aware manner, and the data in the ViewModel lives on even when the Activity Configuration changes, such as when the screen switches between horizontal and vertical screens

Simple use of the viewModel
  1. Join the rely on

Implementation ‘androidx. Lifecycle: lifecycle – viewmodel – KTX: 2.3.0’

  1. Create a class that inherits the ViewModel and holds the data required by the project. You can either inherit the viewModel or inherit the AndroidViewModel, but the difference is that the AndroidViewModel has an application environment
class PictureInfoViewModel : ViewModel() {

    lateinit var uriList: ArrayList<Uri>
    var currentPicture: MutableLiveData<Int> = MutableLiveData()
}

class PictureInfoViewModel(application: Application) : AndroidViewModel(application) {

    lateinit var uriList: ArrayList<Uri>
    var currentPicture: MutableLiveData<Int> = MutableLiveData()

}
Copy the code
  1. In old versions, ViewModelProviders were used to create viewModel providers. This class was deprecated due to poor scalability. It is recommended to use ViewModelProvider instead. We need to pass in one more parameter that implements the Factory class create method, which seems like a lot of trouble to pass in, but we can create a Factory implementation class that customizes the Create method to build the viewModel, and if you don’t want to go through the hassle of overwriting the method, You can use your system’s NewInstanceFactory or AndroidViewModelFactory directly to get a factory object, The two differences are that the AndroidViewModelFactory needs to be passed in to the Application, just to be used by the Previous AndroidViewModel. Here are a few common ways to create them
// Use the ViewModelProvider class to build a delegate using the by keyword.
    private val viewModel: PictureInfoViewModel by lazy {
        ViewModelProvider(
            this,
            ViewModelProvider.NewInstanceFactory()			// We can use the system or implement the Factory
        ).get(PictureInfoViewModel::class.java)
    }

// Method 2: Use the extension function ComponentActivity provides to build, if there is no special need for Factory, recommended to use this method, is simple and convenient
private val viewModel: PictureInfoViewModel by viewModels()

// Extend the implementation of the function, passed in Factory is not empty, empty to go to the default process
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    valfactoryPromise = factoryProducer ? : { defaultViewModelProviderFactory }return ViewModelLazy(VM::class.{ viewModelStore }.factoryPromise)
}
Copy the code
  • Take a look at the official viewModel lifecycle diagram
  • You can see that the viewModel is destroyed only when the Activity is destroyed

Source code analysis

Creation method analysis

ViewModelProvider(Store, factory).get(viewModelClass.java)

  • Here is a viewModel repository and a factory to build the viewModel
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }


// The ViewModelStore is a hashMap that caches the ViewModel
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());
    }

    /** * Clears internal storage and notifies ViewModels that they are no longer used. */
    public final void clear() {
        for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
  • The process is to first retrieve the viewModel from the viewModel repository by Key, and then create the viewModel by reflecting the class passed in by get
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        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
  • Back when we created it, why does passing this get us the viewModelStore? To find the source, the original is the Activity of the grandfather class ComponentActivity, the implementation of ViewModelStoreOwner interface
public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

	// ComponentAcitivty
    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;
    }

	/ / to continue to see, if is empty, try from getLastNonConfigurationInstance () to get the nc object
	/ / to get viweModelstore from nc inside, getLastNonConfigurationInstance what's behind our analysis
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}}Copy the code
  • Lifecycle adds a listener when Lifecycle is initialized, as we all know, Lifecycle lifecycle changes will be called back to this onStateChanged method. If the event called back is deStory (the activity called onDestroy), the following judgment will be entered, Go to getViewModelStore().clear() and clear the viewModel
 public ComponentActivity() {
        ..................


		// Ignore irrelevant code
        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

To summarize: The viewModel is created using the Factory and cached in acitivty’s viewModelStore. This is the result of haspMap data that can hold many viewModels. When acitivty destroys the viewModel, Destroy the viewModel via a listening callback to Lifecycle

Activity Recovers data from state changes

Let’s review the life cycle of interface rotation onPause()->onStop()->onSaveInstanceState()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState()-> onResume()

  • Thus, data recovery method 1 can be obtained: Rewrite onSaveInstanceState to store important temporary data that we want to keep in the Bundle, and the next time we restart the Activity, we’ll execute onCreate, We can retrieve the data from the Bundle object that we accept as an argument to the onCreate method. Or pull data out of onRestoreInstanceState

  • Second way: by Activity provides two methods, onRetainNonConfigurationInstance to save the data, and then through getLastNonConfigurationInstance save data recovery. Attentive students should be found, the ViewModel is the last saved data is acquired by getLastNonConfigurationInstance, Google has not recommended we rewrite, because it has been a ViewModel

  • Specific ViewModel data preservation and recovery code

		// Save data
	    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last 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;
        nci.viewModelStore = viewModelStore;
        return nci;
    }


	/ / data recovery, here is the code which we analysis the creation process, the last viewModel data obtained through getLastNonConfigurationInstance
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}}Copy the code

Summary: Why does the ViewModel not lose state after rotating the screen?

  • Through two methods getLastNonConfigurationInstance and onRetainNonConfigurationInstance, when the device configuration changes (such as screen rotation), AMS by binder to the NonConfigurationInstances onRetainNonConfigurationInstance callback instances of an object, The viewModelStore holds all the viewModel information for an activity. When the activity is rebuilt and the viewModel is created, Can call getLastNonConfigurationInstance () from nc find viewModelStore inside, and then take out need a viewModel, complete data recovery