introduce

A ViewModel is something between a View and a Model. It acts as a bridge, allowing views and data to be separated and still communicate.

The ViewModel strips the data needed for the page from the page, which only needs to handle user interaction and present the data.

Viewmodels typically notify the page of changes in data and update the UI through either LiveData or DataBinding

ViewModel lifecycle

Viewmodels are always created with the Activity/Fragment being created and destroyed with the Activity/Fragment being destroyed

As you can see from this ViewModel lifecycle, the ViewModel is not destroyed when the screen rotates and the Activity is destroyed and recreated, which helps us retrieve data to update the UI after the Activity is recreated. It has an advantage over onSaveInstanceState because onSaveInstanceState is not suitable for recovery operations with a large amount of data. It can only recover a small amount of data that is serialized and deserialized, whereas ViewModel supports not only a large amount of data, Serialization and deserialization operations are not required.

Basic usage

Add dependencies:

Create a ViewModel classes
class LiveDataViewModel:ViewModel() { private var timer: Timer? = null lateinit var mListener: OnTimeChangeListener var currentSecond = 0 fun startTiming(){ if(timer==null){ timer = Timer() timer? .schedule(object :TimerTask(){override fun run() {currentSecond ++ mlistener.onchanged (currentSecond)}},0,1000)}} fun setTimeListener(listener: OnTimeChangeListener){ this.mListener = listener } override fun onCleared() { super.onCleared() timer? .cancel() } interface OnTimeChangeListener{ fun onChanged(second:Int) } }Copy the code
Create the ViewModel in the activity and call the functions inside it to update the data
class MainActivity : AppCompatActivity() { var binding: ActivityMainBinding ? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding=DataBindingUtil.setContentView(this,R.layout.activity_main) val bean = User("arrom", 18); binding? .user = bean; val model = ViewModelProvider(this)[LiveDataViewModel::class.java] model.setTimeListener(object :LiveDataViewModel.OnTimeChangeListener{ override fun onChanged(second: Int) { Log.d("MainActivity","onChanged${second}") bean.name = "arrom-${second}" bean.notifyChange() } }) Binding?.clickBtn?.setonClickListener {model.startTiming() log.d ("MainActivity"," button click event ")}}Copy the code

Source code analysis

val model = ViewModelProvider(this)[LiveDataViewModel::class.java]
Copy the code

Enter the ViewModelProvider class

public constructor(
    owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner))
Copy the code
 */
public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
) 
Copy the code

To create a ViewModelProvider, you must pass in two parameters:

  • The ViewModelStore is used to store and manage viewModels

  • Factory is used to instantiate the viewModel

The ViewModelProvider provides methods instantiated in 3:

  1. Simple factory pattern construction
  2. Construct an instance by reflection
  3. Implement the Factory construct instance yourself
NewInstanceFactory construct instance
@Suppress("SingletonConstructor") public open class NewInstanceFactory : Factory { @Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T {return try {// return 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) } } public companion object { private var sInstance: NewInstanceFactory? = null @JvmStatic public val instance: NewInstanceFactory @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) get() { if (sInstance == null) { sInstance = NewInstanceFactory() } return sInstance!! }}}Copy the code

When we construct an instance using NewInstanceFactory(), we can see from the source code that it implements the Factory interface and calls the newInstance method of the ViewModel we passed in the previous GET method in its internal create method

AndroidViewModelFactory construct instance
public open class AndroidViewModelFactory( private val application: Application ) : NewInstanceFactory() { @Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T {return if (AndroidViewModel: : class. Java. IsAssignableFrom (modelClass)) {try {/ / by reflection for instance objects modelClass.getConstructor(Application::class.java).newInstance(application) } catch (e: NoSuchMethodException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InstantiationException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InvocationTargetException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } } else super.create(modelClass) } public companion object { internal fun defaultFactory(owner: ViewModelStoreOwner): Factory = if (owner is HasDefaultViewModelProviderFactory) owner.defaultViewModelProviderFactory else instance internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey" private var sInstance: AndroidViewModelFactory? = null @JvmStatic public fun getInstance(application: Application): AndroidViewModelFactory { if (sInstance == null) { sInstance = AndroidViewModelFactory(application) } return sInstance!!  }}}Copy the code
Implement the Factory construct instance yourself
public interface Factory { public fun <T : ViewModel> create(modelClass: Class<T>): T } 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 { @Suppress("ControlFlowWithEmptyBody") if (viewModel ! = null) { // TODO: log a warning. } } viewModel = if (factory is KeyedFactory) { factory.create(key, modelClass) } else { factory.create(modelClass) } store.put(key, viewModel) return viewModel }Copy the code

When we construct the ViewModelProvider, we save the two parameters we pass in. When we call the get method, the method first retrieves the ViewModel from the ViewModelStore by key. This key is the DEFAULT_KEY + class name above. If found, return directly, otherwise call factory create method to create ViewModel instance into ViewModelStore for management and save, and return the instance

Enter the ViewModelStore class

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

The ViewModel is managed using a HashMap

ViewModelStoreOwner analysis

When we get the ViewModelStore, we get it through a ViewModelStoreOwner interface

When we create a ViewModelProvider, we pass in either a fragment or an activity, and the fragment is actually the activity’s getViewModelStore method

ComponentActivity class

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."); } // If the current component's mViewModelStore is empty, To get if from NonConfigurationInstances (mViewModelStore = = null) {NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; In NonConfigurationInstances} / / if not found, Create a new ViewModelStore and save it if (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }Copy the code

GetViewModelStore to get a ViewModelStore, when ViewModelStore is empty, Will call getLastNonConfigurationInstance method to restore NonConfigurationInstances before state changes, and this method is the corresponding onRetainNonConfigurationInstance, This method is called by the system to save data information when the component state changes.

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

The timing of the viewModelStore assignment

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

In the state of the components is changed, will try to get viewModelStore, and to save viewModelStore in NonConfigurationInstances and return.

In onRetainNonConfigurationInstance to save viewModelStore in getLastNonConfigurationInstance viewModelStore for recovery

summary

When we instantiate the ViewModelProvider in an activity, the dependent activity internally calls the getViewModelStore method to get a ViewModelStore, or creates one if it doesn’t, and saves it. Then call the viewModelProvider.get method to save our ViewModel instance through the ViewModelStore. When our activity state changes, such as rotating the screen, The system calls onRetainNonConfigurationInstance method, in this way will our ViewModelStore to save.