What is a ViewModel?

Official explanation:

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

Personal Understanding:

The ViewModel is the bridge layer between UI and data. It assumes the data processing logic of the UI layer (Activity/Fragment), and has its own life cycle to maintain, and can still save data when the screen rotates. Below is an official view of the ViewModel’s life cycle as the screen rotates.

How do I use ViewModel?

Here is an example of an Activity.

  1. Custom ViewModel class that inherits from ViewModel. This class is used for data processing and returns data to the UI
// This is just an example. It is recommended to use LiveData together.
class CustomViewModel: ViewMdoel() {private val data: List<String> = mutableListOf()
    
    fun getData(a): List<String> {
        retrun data}}Copy the code
  1. Get the custom ViewModel class in the Activity and call the corresponding method to get the processed data
// Here is an example of an Activity, Fragment similar.
class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
    
        // Get the custom ViewModel class through KTX
        / / need to add the dependent on implementation 'androidx. Lifecycle: lifecycle - viewmodel - KTX: $viewmodel_version'
        //val viewModel: CustomViewModel by viewModels()
        
        // Get the custom ViewModel class the traditional way
        val viewModel = ViewModelProvider(this).get(CustomViewModel::class.java)
        
        // Get the data to implement the subsequent UI logic
        val data = viewModel.getData()
    }
}
Copy the code

What does the ViewModel work on?

The principle only explains the Activity part. The source code is based on Lifecycle 2.2.0 and Acitivity 1.1.0.

Source code analysis is divided into two parts, starting from the call method, generally know the internal logic, and then start from the question, to answer the doubts in the mind.

Realize the principle of

Start by calling the method

// Get the custom ViewModel object via ViewModelProvider#get(), and then call the relevant method
ViewModelProvider(this).get(CustomViewModel::class.java)
Copy the code

As you can see by calling methods, you need to understand the internal implementation of the ViewModelProvider() and Get () methods.

ViewModelProvider() internal implementation:

public class ViewModelProvider {

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

    / /... Omit...

    // The input parameter is ViewModelStoreOwner, which is used to obtain ViewModelStore and ViewModelFactory
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
    }

    /**
     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
     * {@code Factory} and retain them in the given {@code store}.
     *
     * @param store   {@code ViewModelStore} where ViewModels will be stored.
     * @param factory factory a {@code Factory} which will be used to instantiate
     *                new {@code ViewModels}
     */
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

    / /... Omit...
}
Copy the code

As you can see from the code above, the ViewModelStore and Factory maintained internally by the ViewModelProvider are created with the owner passed in. The owner of this call is the Activity, so let’s see how it is created in the Activity.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory {

     / /... Omit...

    @NonNull
    @Override
    public ViewModelStore getViewModelStore(a) {
        / /... Omit...
        
        if (mViewModelStore == null) {
            // From the last configuration change (such as screen rotation)
            NonConfigurationInstances nc =
                   (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                // Create it directly
                // ViewModelStore contains only one Map to store the created ViewModels
                mViewModelStore = newViewModelStore(); }}return mViewModelStore;
    }

    / /... Omit...

    @NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory(a) {
        / /... Omit...
        
        // Check whether there is a create, if there is no create directly.
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this, getIntent() ! =null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

    / /... Omit...
}
Copy the code

Through the above code can know, ComponentActivity ViewModelStoreOwner and HasDefaultViewModelProviderFactory interface, Create the ViewModelStore and SavedStateViewModelFactory objects respectively. The ViewModelStore maintains a Map internally to store the created ViewModels. SavedStateViewModelFactory internal is to maintain the ViewModel to create, is a factory class. Ok, so much for the internal logic of the ViewModelProvider constructor.

ViewModelProvider#get()

public class ViewModelProvider {

    private static final String DEFAULT_KEY =
                "androidx.lifecycle.ViewModelProvider.DefaultKey";
    
    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;
    
    / /... Omit...

    @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");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // Fetch from the cache
        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 the cache does not exist, create it.
        / / according to the above "ViewModelProvider () the internal implementation" learn that mFactory = new SavedStateViewModelFactory (),
        / / and SavedStateViewModelFactory realize ViewModelProvider KeyedFactory interface.
        // mFactory instanceof KeyedFactory = true
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        // Store the newly created ViewModel in the cache
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
    
    / /... Omit...
}
Copy the code

By the above code, the get () method is first obtained from the ViewModelStore internal ever created, if not create, by SavedStateViewModelFactory# the create () method to create, And store the newly created ViewModel in the ViewModelStore. So you see SavedStateViewModelFactory# within the create ().

public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {

    private final Application mApplication;
    private final ViewModelProvider.AndroidViewModelFactory mFactory;
    private final Bundle mDefaultArgs;
    private final Lifecycle mLifecycle;
    private final SavedStateRegistry mSavedStateRegistry;
    
    / /... Omit...
    
    @SuppressLint("LambdaLast")
    public SavedStateViewModelFactory(@NonNull Application application,
            @NonNull SavedStateRegistryOwner owner,
            @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        AndroidViewModel; AndroidViewModel; AndroidViewModel; AndroidViewModel; AndroidViewModel
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            // Internally determines whether the Class constructor passed in contains the specified arguments.
            // Our custom ViewModel constructor does not contain any arguments, so null is returned
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
      
        if (constructor == null) {
            MFactory# Create () is called.
            return mFactory.create(modelClass);
        }

        / /... Omit...
    }
    
    / /... Omit...
}    
Copy the code

By the above code can be found that SavedStateViewModelFactory# the create () internal did some checking, but failed to meet our custom ViewModel, Finally through ViewModelProvider. AndroidViewModelFactory. GetInstance (application) # the create () to create. So look at the internal implementation of AndroidViewModelFactory#create().

public class ViewModelProvider {
    / /... Omit...
    
    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
        / /... Omit...
    
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            // Check whether ViewModel inherits from AndroidViewModel. Our custom ViewModel inherits from the ViewModel.
            / / so finally called super. The create (), namely ViewModelProvider. NewInstanceFactory# the create ()
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}return super.create(modelClass); }}/ /... Omit...
    
    public static class NewInstanceFactory implements Factory {
        / /... Omit...
    
        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            try {
                // Create this stance directly through class.newinstance ().
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}}/ /... Omit...
}
Copy the code

AndroidViewModelFactory#create is checked, and NewInstanceFactory#create() is called. This method creates the ViewModel directly through class.newinstance (). At this point, the ViewModel creation process is complete. There is a summary of this process at the end of the article.

Start with questions

1. How does the ViewModel keep data while the screen rotates?

About screen rotation can still reserve the key data, actually need to know Activity# onRetainNonConfigurationInstance (), by the method of annotation can know, when there are new configuration cause to create an instance, system will be informed by this method the callback.

public class Activity{

     / /... Omit...
    
     /**
     * 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. * * ... Omit... * * /
    public Object onRetainNonConfigurationInstance(a) {
        return null;
    }

    / /... Omit...
}
Copy the code

ComponentActivity overrides this method to hold viewModelStore in it when the screen rotates, so that when the screen rotates, re-go getViewModelStore(), Pass getLastNonConfigurationInstances viewModelStore before () method can get. PS: need to pay attention to some here, ComponentActivity# onRetainNonConfigurationInstance () mentioned the bar his rewrite the method!

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory {
    / /... Omit...
        
    @NonNull
    @Override
    public ViewModelStore getViewModelStore(a) {
        if (mViewModelStore == null) {
            // From the last configuration change (such as screen rotation)
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = newViewModelStore(); }}return mViewModelStore;
    }
    
    / /... Omit...

    /**
     * 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(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;
    }
    
    / /... Omit...
}
Copy the code

2. How does the ViewModel manage the lifecycle and clean up the data when the Activity is destroyed?

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory {
    / /... Omit...

    public ComponentActivity(a) {
        Lifecycle lifecycle = getLifecycle();
        
        / /... Omit...
        
        If Event = ON_DESTROY && no configuration update is received, it is destroyed and all data is cleared.
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}});/ /... Omit...
    }

    / /... Omit...
}
Copy the code

As you can see from the above code, relying on Lifecycle to provide Lifecycle events, listening for Activity Lifecycle through addObserver() when ON_DESTROY is received and not destroyed as a result of a configuration update (screen rotation), Call ViewModelStore#clear() to clean up all the data.

3. How to share data between fragments when there are multiple fragments in a single Activity?

Through the source code analysis of this problem has actually come to a conclusion. When the ViewModelProvider() method is called, getActivity() is passed in. The ViewModelStore and Factory are created in the Activity, so the ViewModel data is also in the Activity. So it’s like using an Activity to maintain data, and then any Fragment based on that Activity can access that data.

conclusion

Briefly summarize the implementation principle of ViewModel: Call the ViewModelProvider() method, which internally relies back on the ViewModelStore and Factory implementations through the interface (in this case, back on ComponentActivity). It then calls the ViewModelProvider#get() method, which internally retrieves the cache from the ViewModelStore. If it doesn’t, it calls Factory# Create () to create it. Final call ViewModelProvider. NewInstanceFactory# the create () method, through the Class. The newInstance () is created, Finally, store the newly created ViewModel in the ViewModelStore. The following is the call sequence diagram, you can refer to the source code.