ViewModels is a fragment extension, which is used to retrieve ViewModel instances. There are two main functions, one is to lazy load the singleton, one is to obtain instances in the Activity or fragment lifecycle, so how to implement, directly see the source code.
use
Let’s say I’m on the login page, and I need to get the login ViewModel in the Activity:
- Use the viewModels function directly, passing a factory as an argument.
private val loginViewModel : LoginViewModel by viewModels {
InjectorUtils.provideLoginViewModelFactory(this)
}
Copy the code
- Provide a ViewModel factory.
/ * * * provide login page logic ViewModelFactory * * / fun provideLoginViewModelFactory (context: context) : LoginViewModelFactory{ return LoginViewModelFactory(getLoginRepository()) }Copy the code
- Create a ViewModel based on the Repository that the ViewModel needs.
class LoginViewModelFactory(private val loginRepository: LoginRepository) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel? > create(modelClass: Class<T>): T { return LoginViewModel(loginRepository) as T } }Copy the code
Source code analysis
1, look at the viewModels source code and parse:
@MainThread inline fun <reified VM : ViewModel> ComponentActivity.viewModels( noinline factoryProducer: (() -> Factory)? = null ): Lazy<VM> { val factoryPromise = factoryProducer ? : { defaultViewModelProviderFactory } return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise) }Copy the code
- First, the class’s comments:
Returns a [Lazy] delegate to access the ComponentActivity's ViewModel, if [factoryProducer]
is specified then [ViewModelProvider.Factory] returned by it will be used
to create [ViewModel] first time.
Copy the code
Return an instance of a Lazy to access this Activity the ViewModel, if factoryProducer namely Factory producers are specified, and then ViewModelProvider. The Factory will be back, This instance will also be used to create the ViewModel instance first.
- Now look at the call timing:
This property can be accessed only after the Activity is attached to the Application,
and access prior to that will result in IllegalArgumentException.
Copy the code
This property is accessible when the Activity is attached, and an exception will be raised if the Activity is accessed early.
2, cooperate with the above comments, we need a ViewModelProvider here. Factory as an example, so here also use NewInstanceFactory directly to complete, direct look at the code:
class LoginViewModelFactory(private val loginRepository: LoginRepository) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel? > create(modelClass: Class<T>): T { return LoginViewModel(loginRepository) as T } }Copy the code
So this is directly returning the LoginViewModel instance.
In 1 we can see that a method with 3 arguments is called. Let’s take a look at the code:
class ViewModelLazy<VM : ViewModel> ( private val viewModelClass: KClass<VM>, private val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory ) : Lazy<VM> { private var cached: VM? = null override val value: VM get() { val viewModel = cached return if (viewModel == null) { val factory = factoryProducer() val store = storeProducer() ViewModelProvider(store, factory).get(viewModelClass.java).also { cached = it } } else { viewModel } } override fun isInitialized() = cached ! = null }Copy the code
- Here’s a very important note:
Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
an activity), associated with this `ViewModelProvider`.
Copy the code
Return an existing ViewModel instance or create an instance, within a scope (fragment or activity), to show that the instance is not created randomly, and is lifecycle dependent.
- Here the implementation of delayed initialization or comparison of the value of learning, there is a need for reference.
- When the viewModel instance is empty, the ViewModelProvider method is finally called to get it.
4, ViewModelProvider code parsing:
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
Copy the code
So, we’re going to create a ViewModelProvider instance with two parameters, the first factory is the factory that we’re going to use to create the ViewModel instance, and the second store is the set of ViewModel instances that we’re going to hold, and the thing to notice here is, if you look at the scope of the store, After all, it can’t hold a ViewModel for too long. If my Activity is destroyed, the store can’t hold any viewModels related to the Activity.
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 (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }Copy the code
Not surprisingly, the store is associated with the activity, which is a HashMap:
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
There is a clear method, which is expected to be called when the activity is destroyed. Take a look at the activity code:
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
And so it is.
A ViewModelProvider corresponds to a store shared by a factory and an Activity, so it must be in this order:
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
- Again, it’s pulled out of the Store, created using the Factory if not, and then inserted into the HashMap.
- There is A new requirement, for example, if I get A ViewModel instance at A of Activity A, but if I get it at B, I need to make A change, which I can do using OnRequeryFactory.
conclusion
Not only does getting a ViewModel instance allow for lazy singleton loading, but internally a HashMap is implemented to save the instance in a certain scope to prevent memory usage.