Introduction to the

The ViewModel stores and manages data related to the interface in a life-cycle manner. Allows data to survive configuration changes such as screen rotation. At the same time, data operations can be separated from the UI controller (Activity), so that only the Activity controls the UI logic and does not need to deal with the data business logic. This eliminates a lot of maintenance work in the Activity when it needs to do something asynchronous, and avoids potential memory leaks when the Activity is destroyed.

To summarize the main advantages:

It is easier to separate the data manipulation logic from the Activity.

Implementation and basic use of ViewModel

JetPack provides the ViewModel helper class for the UI controller, which is responsible for preparing data for the interface. ViewModel objects are automatically retained during configuration changes so that the data they store is immediately available for use by the next activity or Fragment instance.

The code is as follows:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<String>> users;
    public LiveData<List<String>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<String>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {

    }
}

Copy the code

Use:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class); model.getUsers().observe(this, users -> { }); }}Copy the code

The creation of the ViewModel

Note that in the code above, the ViewModel is not created using new MyViewModel(). Instead use:

MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

The corresponding in Kotlin:

private val myViewModel: MyViewModel by viewModels();

Conclusion: Using the ViewModelProvider to create a ViewModel ensures that when the same Activity is recreated, it receives the same MyViewModel instance as the ViewModel instance created by the first Activity. It is simply understood that it is a singleton.

Let’s see how the source code is implemented:

First look at the constructor and implementation of the ViewModelProvider:


private final Factory mFactory;

private final ViewModelStore mViewModelStore;

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 constructor is simple: create a ViewModelStore and Factory

The preparatory work

ViewModelStore parsing

The ViewModelStore code is as follows:


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 code is simple: a HashMap stores the key-value pairs of the ViewModel.

ViewModelStore is obtained by interface ViewModelStoreOwner’s getViewModelStore() method. Taking ComponentActivity as an example, the specific implementation is as follows:

//ComponentActivity.java: static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; } @NonNull @Override 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; } @SuppressWarnings("WeakerAccess") /* synthetic access */ void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}}Copy the code

With getLastNonConfigurationInstance method is actually a return to an activity, can also be understood as a ComponentActivity instance, here don’t do in-depth discussions, the specific implementation of the activity is:

@Nullable public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances ! = null? mLastNonConfigurationInstances.activity : null; }Copy the code

As you can see, every Activity holds a mViewModelStore, and the ensureViewModelStore() method ensures that all viewModels get the same ViewModelStore. The Activity uses HaspMap to manage the ViewModel by holding mViewModelStore. It is also not hard to see that the activity and ViewModel have a one-to-many relationship.

The Factory parsing

Factory, as its name implies, is a Factory pattern. It is an interface defined in the ViewModelProvider, and the code is simple:


public interface Factory {

    @NonNull

    <T extends ViewModel> T create(@NonNull Class<T> modelClass);

}

Copy the code

That’s what we’re going to create the ViewModel for.

Take AppCompatActivity as an example: It implements ViewModelStoreOwner interface by inheriting ComponentActivity, and when ViewModelProvider is initialized, Factory can be implemented as follows: NewInstanceFactory. GetInstance ();

The specific code is as follows:

public static class NewInstanceFactory implements Factory { private static NewInstanceFactory sInstance; @NonNull static NewInstanceFactory getInstance() { if (sInstance == null) { sInstance = new NewInstanceFactory(); } return sInstance; } @SuppressWarnings("ClassNewInstance") @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { try { 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); }}}Copy the code

A static singleton that returns a factory class to create a ViewModel, using the create() method to create an instance of the ViewModel by reflection.

Actually creating

Now that the preparation is complete, let’s see how the ViewModel is created:

@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) {// First check whether there is an instance of the current ViewModel in Map ViewModel ViewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } return (T) viewModel; } else { if (viewModel ! 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

As you can see, we first use the class name as the key value to determine whether the ViewModelStore held by the Activity contains an instance of the current ViewModel. If not, create an instance from factory.create above and add it to mViewModelStore.

To sum up:

  1. The Activity holds all instantiated ViewModels through a HashMap.

  2. The ViewModelProvider creates and retrieves viewModels through a singleton factory pattern and a ViewModelStore, also known as a HashMap, to ensure that the ViewModel remains a singleton in the current Activity.

ViewModel lifecycle

The ViewModel object exists for a time Lifecycle that is passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel will remain in memory until Lifecycle is gone permanently: for an activity, when the activity completes; For fragments, this is when the fragment is separating.

Its life cycle is shown in the figure below:

The ViewModel is typically created/retrieved in the Activity’s onCreate() method. If you call onCreate() again while rotating the device screen, in conjunction with the creation of the ViewModel above, you do not actually create a new ViewModel instance, but instead pull an existing instance from the HashMap.

Thus: The ViewModel exists from the time you first request the ViewModel until the activity completes and is destroyed.

The destruction of the ViewModel

Destroy source code as follows:

@SuppressWarnings("WeakerAccess") protected void onCleared() { } @MainThread final void clear() { if (mBagOfTags ! = null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // see comment for the similar call in setTagIfAbsent closeWithRuntimeException(value); } } } onCleared(); }Copy the code

Where: clear() method is called in ViewModelStore, and its related code has been shown above:

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}
Copy the code

Viewmodelstore.clear () is called in the Activity:

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

The Lifecycle of an Activity managed through Lifecycle is called when the Activity is destroyed, that is, onDestory.

ViewModel implementation with parameters

Since the ViewModel cannot be initialized with the new keyword, what happens if you need to pass parameters to the ViewModel when you need to reinitialize it? Such as:

public class MyViewModel extends ViewModel {

    private String tag;

    public MyViewModel(String tag) {
        this.tag = tag;
    }

    private MutableLiveData<List<String>> users;

    public LiveData<List<String>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<String>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {

    }
}
Copy the code

We need to override the Factory create method and create a Factory class using the ViewModelProvider constructor:

class MyViewModelFactory implements ViewModelProvider.Factory { private String tag; public MyViewModelFactory(String tag) { this.tag = tag; } @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { return (T) new MyViewModel(tag); }}Copy the code

Concrete implementation:


MyViewModelFactory myViewModelFactory = new MyViewModelFactory("TAG");

MyViewModel model = new ViewModelProvider(this,myViewModelFactory).get(MyViewModel.class);

Copy the code

Of course, you can also use anonymous inner classes:

MyViewModel model = new ViewModelProvider(this, new ViewModelProvider.Factory(){
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return (T) new MyViewModel("tag");
    }
}).get(MyViewModel.class);

Copy the code

Extensions in KOLin

The use of extension functions in Koltin simplifies the implementation of the ViewModel:

@MainThread public inline fun <reified VM : ViewModel> Fragment.viewModels( noinline ownerProducer: () -> ViewModelStoreOwner = { this }, noinline factoryProducer: (() -> Factory)? = null ): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer) @MainThread public fun <VM : ViewModel> Fragment.createViewModelLazy( viewModelClass: KClass<VM>, storeProducer: () -> ViewModelStore, factoryProducer: (() -> Factory)? = null ): Lazy<VM> { val factoryPromise = factoryProducer ? : { defaultViewModelProviderFactory } return ViewModelLazy(viewModelClass, storeProducer, factoryPromise) }Copy the code
public 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(): Boolean = cached ! = null }Copy the code

It’s essentially created using the ViewModelProvider, but with some Kotlin syntactic sugar added to simplify things.

Not only that, Kotlin also supports using annotations, which saves you the trouble of creating a Factory. The newly interested can explore on their own (Hilt and Jetpack integration)

conclusion

  • An Activity holds a ViewModel. An Activity can have multiple viewModels.

  • A ViewModel of the same type has only one instance in an Activity;

  • A singleton of a single type of ViewModel is guaranteed through HashMap and factory schema;

  • The ViewModel life cycle runs through the Activity and is not created repeatedly.