An overview of

The ViewModel stores only one piece of data during the Activity or Fragment lifecycle. The data in the ViewModel is not lost due to screen rotation or other configurations (such as switching to multi-window mode). However, after finishing () or pressing the Return key, the ViewModel is cleared when the Activity or Fragment goes onDestroy to avoid memory leaks. While the screen rotation Activity will also go onDestroy, it will determine if it was caused by the screen rotation. So ViewModel is a good class for storing data

ViewModel lifecycle

ViewModel objects exist longer than the Activity life cycle. Do not hold activities, fragments, or views in a ViewModel. This will cause memory leaks. If we need to reference a Context in a ViewModel, Google provides the AndroidViewModel class for us to use.

Three ViewModel benefits

  1. Will not be destroyed due to screen rotation or other configuration changes (such as switching to multi-window mode) to avoid data re-creation, such as network data reloads
  2. When an Activity or Fragment is destroyed, it automatically clears data, which is bound to the Activity or Fragment’s life cycle, avoiding memory leaks.
  3. Better data transfer and interaction between multiple fragments or between activities and fragments
  4. Reduce the stress on Activityu or Fragment, which simply display the data interface to help the Activity and Fragment process some of the data logic
  5. You can do a new ViewModle, but it basically doesn’t have any ViewModle features. This is equivalent to a member property

3.1 Since the example on the official website is a bit old, write a shared example

It is common for two or more fragments in an Activity to need to communicate with each other. Activities and fragments call each other. Here is a call between an Activity and two fragments

Specific Demo addressgithub

3.1.1 Build an Avtivity

class ViewModelDemoActivity : AppCompatActivity() {
    // Create a ViewModel with the Activity dimension
    private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
       var binding = DataBindingUtil.setContentView<ActivityViewmodelDemoBinding>(
            this,
            R.layout.activity_viewmodel_demo
        )
        viewModel.dataLive.observe(this.object : Observer<String> {
            override fun onChanged(s: String?). {
                // When the screen is changed, the View is redrawn, but the user has a value immediately
                tv_name.text = s
            }
        })

        tv_name.setOnClickListener{
            // Click in the activity to change the value
            viewModel.dataLive.value= "Activity-triggered changes"
        }
        viewModel.getName()
    }
    companion object {
        fun startMe(activity: Activity) {
            activity.startActivity(Intent(activity, ViewModelDemoActivity::class.java))
        }
    }
}
Copy the code

3.1.2 ViewModel code

class DemoViewModel : ViewModel() {
     val dataLive: MutableLiveData<String> =
         MutableLiveData<String>()


    fun getName(a){
        viewModelScope.launch {
            delay(1000)
            dataLive.value = Honor of Kings}}}Copy the code

3.1.3 Layout file

Two fragments are statically added to the Activity


      
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ED6E6E"
            android:textSize="30sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <fragment
            android:id="@+id/one"
            android:name="com.nzy.mvvmsimple.viewmodel.ShareOneFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="@+id/guidline"
            app:layout_constraintTop_toBottomOf="@+id/tv_name" />

        <fragment
            android:id="@+id/two"
            app:layout_constraintTop_toBottomOf="@+id/tv_name"
            android:name="com.nzy.mvvmsimple.viewmodel.ShareTwoFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="@+id/guidline"
            app:layout_constraintRight_toRightOf="parent"
            />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.5" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

3.1.4 two fragments

Note: Both fragments get the dimensions of the ViewModel, with the Activity as the dimension

class ShareOneFragment : Fragment() {
    // Create the ViewModel with the Activity dimension. Note that requireActivity() is not getActivity(). RequireActivity () cannot be empty, and getActivity() can be empty
    private val viewModel: DemoViewModel
            by lazy { ViewModelProvider(requireActivity()).get(DemoViewModel::class.java) }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        return inflater.inflate(R.layout.fragment_share_one, container, false)}override fun onActivityCreated(savedInstanceState: Bundle?). {
        super.onActivityCreated(savedInstanceState)
        ViewLifecycleOwner(getViewLifecycleOwner())) is passed instead of fragmenting itself
        viewModel.dataLive.observe(viewLifecycleOwner, Observer {
            tv_name_one.text = it
        })

        // Change the value in FragmentOne
        tv_one.setOnClickListener {
            viewModel.dataLive.value = "Fragment-one, changed value"}}companion object {
        @JvmStatic
        fun newInstance(a) =
            ShareOneFragment()
    }
}

Copy the code

ViewModel and onSaveInstanceState

4.1 the ViewModel

  1. Cached in memory, local cache and network cache read and write faster
  2. You can store large values, such as bitmaps, large objects, and so on
  3. Data is bound to the Activity or Fragment lifecycle, and when the Activity shuts down normally, the data in the ViewModel is erased. Such as
    • Call finish ()
    • Press the Back key to exit,
    • The user killed the process directly or ran out of memory in the background, which was reclaimed
  4. When the Activity spins or other configuration changes, there are values in the ViewModel that will not be destroyed.
  5. ViewModel can also use the local store, through SavedStateHandle implementation, using SavedStateViewModelFactory this factory, there is not much introduced, is the official version, a reference isImplementation "androidx lifecycle: lifecycle - viewmodel - savedstate: 2.2.0

6, you can also do new ViewModle, new basically doesn’t have ViewModle features anymore. This is a real Present layer

4.2 onSaveInstanceState

  1. Only suitable for small amounts of data that can be serialized and deserialized
  2. Not suitable for potentially large amounts of data, such as arrays or bitmaps. You can store simple primitive types and simple small objects, such as strings, and ids
  3. The data is stored on disk

Note For complex and large data, use a database or SharedPreferences

ViewModel source code analysis

The fragments and activities below are from the AndroidX package and Lifecycle is 2.2.0,

  • Do not create a New ViewModle, new is just like normal, lost meaning, ViewModelProVider is also created by reflection of the ViewModel
  • When you create a ViewModelProvider, you create a ViewModelStore (in ComponentActivity), and in ViewModelStore you create a HashMap, To store all viewModels in the current ViewModelProvider. When the Activity triggers the Destory, it iterates through the ViewModel, allowing us to clear out our own things, such as network requests
  • NonConfigurationInstances, when creating the current getViewModelStore (in ComponentActivity), From access to the latest data screen when switching preserved NonConfigurationInstances, this thing can save the whole Activity of all things, get ViewModelStore from here
  • When somehow the screen switch or config change triggers onRetainNonConfigurationInstance, stored here for ViewModelStore, namely the ViewModel is not lost, or the ViewModel before, When in the Activity to find the last getLastNonConfigurationInstance,
  • When you call get, it goes from the ViewModelStore, and if it goes directly, it doesn’t go directly from the Factory.
  • In ComponentActivity it listens for the Activity lifecycle, and in onDestory it clears ViewModelStore if it is not caused by isChangingConfigurations
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(); }}}});Copy the code

5.1 the source code

ViewModelProvider ViewModel source rarely, is basically a map is used to store inside about coroutines and SavedStateHandleController (temporarily know this), the onCleared () method is for our call, When an Activity or Fragmengt is closed, some resources, such as Rxjava streams, need to be cleaned up

// To store something that will be cleared when the Activity closes
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    // To store some data, such as streams in Rxjava, when the Activity is closed
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
Copy the code

5.1.1 won ViewModelProvider

private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }
Copy the code

Look at the constructor of ViewModelProvicder

// Create a ViewModelProvider to create ViewModels and store it in the store given ViewModelStoreOwner. If the owner is a subclass of HasDefaultViewModelProviderFactory, Use HasDefaultViewModelProviderFactory factory, like fragments and ComponentActivity are realized HasDefaultViewModelProviderFactory interface
 public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    
 public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
Copy the code

The default method above is; Used to create the ViewModels, and keep it in the storage area of a given ViewModelStoreOwner, if the owner is a subclass of HasDefaultViewModelProviderFactory, Use HasDefaultViewModelProviderFactory factory, like fragments and ComponentActivity (is FragmentActivity dad, AppCompatActivity grandpa) are realized HasDefaultViewModelProviderFactory interface, get Frctory SavedStateViewModelFactory. Otherwise, we Create the ViewModel with the NewInstanceFactory. For example, if we need to pass parameters in the ViewModel, we can write our own Factory inheriting the NewInstanceFactory, using our own method Create().

5.1.1.1 ComponentActivity to obtain the Factory method

Public ViewModelProvider. Factory getDefaultViewModelProviderFactory () {/ / see if the activity had attache to the application if (getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } if (mDefaultFactory == null) { mDefaultFactory = new SavedStateViewModelFactory( getApplication(), this, getIntent() ! = null ? getIntent().getExtras() : null); } return mDefaultFactory; }Copy the code

Obtain Factory method in 5.1.1.2 Fragment

    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    requireActivity().getApplication(),
                    this,
                    getArguments());
        }
        return mDefaultFactory;
    }
Copy the code

5.1.2 Self-defined Factory

use

private val viewModel:UserViewModel by lazy { ViewModelProvider(this,UserViewModelFactory(repository)).get(UserViewModel::class.java) }
Copy the code

Your own Factory

class UserViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // Pass in a Repository parameter
        return UserViewModel(repository) as T
    }
}
Copy the code

5.1.3 ViewModelStoreOwner: Stores the owner of the ViewModel

Fragment and ComponentActivity (the parent of FragmentActivity, the grandfather of AppCompatActivity) both implement ViewModelStoreOwner.

Public interface ViewModelStoreOwner {// ViewModelStore is the @nonNULL ViewModelStore used to store instances of ViewModel getViewModelStore(); }Copy the code

5.1.3.1 ComponentActivity to obtain ViewModelStroe method

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) {/ / for the last time change NonConfigurationInstances instance, NonConfigurationInstances all activity in it, NonConfigurationInstances is an instance of has nothing to do with the configuration change. NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) {/ / Restore the ViewModelStore from NonConfigurationInstances / / from NonConfigurationInstances ViewModelStore recovery mViewModelStore = nc.viewModelStore; If (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }Copy the code

5.1.3.2 see ComponentActivity: : onRetainNonConfigurationInstance

public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; If (viewModelStore == null) {// If null, Shows no call getViewModelStore before () method, which is never call ViewModelProvider (requireActivity ()), get (DemoViewModel: : class. Java) method to get The ViewModel. So we have a look at the last NonConfigurationInstance exists inside viewModelStore NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! ViewModelStore = nc.viewModelStore; If (viewModelStore == null && custom == null) {return null; } / / if viewModelStore is not null, that is to say the last NonConfigurationInstance there are values, new out NonConfigurationInstances and assignment directly, Back out NonConfigurationInstances nci = new NonConfigurationInstances (); nci.custom = custom; nci.viewModelStore = viewModelStore; retuCopy the code

Then see NonConfigurationInstances class, is a static inner class ComponentActivity, used to store the viewModelStore, whether the configuration change, an instance of this class will not change, the inside of the viewModelStore won’t disappear

  static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
Copy the code

5.1.3.3 retainNonConfigurationInstances method performs to storage NonConfigurationInstances, return the result is NonConfigurationInstances, At the same time store NonConfigurationInstances into ActivityClientRecord, ActivityClientRecord is an Activity book. At the time of the Activity of the Attach, will give a member variable NonConfigurationInstances assignment

Before the Activity call onDestroy will call retainNonConfigurationInstances Activity

 NonConfigurationInstances retainNonConfigurationInstances() {
     ....
 }
Copy the code

To sum up:

  • First take ViewModelStore by NonConfigurationInstances NonConfigurationInstances is a static inner class, not because the configuration change (such as screen rotation), and recreated
  • If NonConfigurationInstances didn’t get that this is a new ViewModelStore, so go create ViewModelStore process directly

5.1.4 ViewModelStore The container used to store viewModels, which contains a HashMap, and the storage key used to store viewModels will be described below

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: map.values ()) {vm.clear(); } mMap.clear(); }}Copy the code

5.1.5 get ViewModel

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    // Get the fully qualified name of the class viewModel
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        // Use a DEFAULT_KEY constant plus the fully qualified name of the class as the key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
Copy the code

Look at the get(String,Class) method

Public <T extends ViewModel> T get(@nonnull String key, @nonnull Class<T> modelClass) { ViewModel ViewModel ViewModel = mViewModelStore.get(key); mViewModelStore.get(key); // Can this object be converted to this class? If viewModel is null, False 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.}} / / mFactory if not himself, that is SavedStateViewModelFactory implementation in KeyedFactory, so take the first time, 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

5.1.6 When to Clear the ViewModel

When the constructor for ComponentActivity has the following code, the ViewModel is clear when the Activity goes to destroy, and there is an isChangingConfigurations method in it, Indicates whether it was caused by a configuration change (such as the Activity screen rotation), and if so, does not clear it.

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(); }}}});Copy the code