Android ViewModel

Mohamed Soliman
6 min readNov 21, 2021

--

In this article, we are going to discuss the internals of ViewModel which is a part of Android Architecture Components. We will first briefly discuss the usages of ViewModel in Android and then we will go in detail about how ViewModel actually works and how it retains itself on configuration changes.

According to the documentation, 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.

The ViewModel the class also helps in implementing the MVVM(Model-View-ViewModel) design pattern which is also the recommended Android app architecture for Android applications by Google.

Also, there are other various advantages of using ViewModel class provided by Android framework like:

  • Handle configuration changes: ViewModel objects are automatically retained whenever activity is recreated due to configuration changes.
  • Lifecycle Awareness: ViewModel objects are also lifecycle-aware. They are automatically cleared when the Lifecycle they are observing gets permanently destroyed.
  • Data Sharing: Data can be easily shared between fragments in an activity using ViewModels.
  • Kotlin-Coroutines support: ViewModel includes support for Kotlin-Coroutines. So, they can be easily integrated for any asynchronous processing.

How does ViewModel works internally?

Link to the sample project

This is the sample project we are going to use for explaining how the viewmodel retains itself on configuration changes.

This is a very simple application with a single activity named MainActivity which shows a counter. The counter value is our view state kept inside the CounterViewModel using a LiveData object. The activity also shows the hashcode of the current activity instance.

On any configuration change, the current activity instance is destroyed and a new instance of the activity is created which makes the hashcode to change. But the counter value is retained as we are keeping it inside our ViewModel and the viewmodels are not destroyed if the activity is getting recreated due to configuration changes.

The first thought around how viewmodels are retained might be that they are stored somewhere at the global(application) level and that’s why they are not destroyed when the activity is recreated. But this assumption is wrong. The viewmodels are stored inside the activity (or FragmentManager in case of fragments).

So let’s discuss how this magic happens and how our viewmodel instance is retained even if the activity is recreated.

viewModel = ViewModelProvider(this, ViewModelFactory()).get(CounterViewModel::class.java)

This is the code to get a viewmodel instance in an activity. As we can see, we are getting an instance of ViewModelProvider by passing two arguments, our activity instance and an instance of ViewModelFactory. Then, we are using this ViewModelProvider instance to get our CounterViewModel object.

Note: In the above example, passing ViewModelFactory is redundant as our CounterViewModel does not have a parameterized constructor. In case we do not pass ViewModelFactory, ViewModelProvider uses a default view model factory.

So the creation of ViewModel involves 2 steps:

  1. Creation of ViewModelProvider
  2. Getting the instance of Viewmodel from ViewModelProvider

Creation of ViewModelProvider

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}

The constructor of ViewModelProvider takes two parameters, ViewModelStoreOwner and Factory. In our example, the activity is the ViewModelStoreOwner and we are passing our own custom factory. ViewModelStoreOwner is a simple interface with a single method named getViewModelStore()

public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}

In the constructor, we are simply getting the ViewModelStore from ViewModelStoreOwner and passing it to the other constructor where they are just assigned to the respective class members.

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}

So now we know that our activity is the ViewModelStoreOwner and its the responsibility of our activity to provide the ViewModelStore.

Getting the ViewModel from ViewModelProvider

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);
}

When we invoke get() method on our ViewModelProvider, it gets the canonical name of the view model class and creates a key by appending the canonical name to a DEFAULT_KEY.

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;
}

After creating the key from the model class, it checks the ViewModelStore (which is provided by our activity) whether a viewmodel instance for the given key is already present or not. If the viewmodel is already present, it simply returns the viewmodel instance present in ViewModelStore and if it’s not present, the ViewModelProvider uses the Factory instance to create a new viewmodel object and also stores it in ViewModelStore.

Now we know that our activity is responsible for storing the ViewModel instances. But it’s still a mystery that how these ViewModel instances are retained even when the activity instance is recreated on configuration change.

How ViewModel retain itself?

As we saw earlier when we created ViewModelProvider we were passing an instance of ViewModelStoreOwner i.e., our activity instance.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner

Our activity implements the ViewModelStoreOwner the interface which has a single method named getViewModelStore()

public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}

Now let’s have a look at the implementation of this method.

@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.");
} //This is true when invoked for the first time
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;
}

Here we can see that getViewModelStore() returns the mViewModelStore. When our activity is recreated due to any configuration change, nc(NonConfigurationInstances) contains the previous instance of ViewModelStore. nc(NonConfigurationInstances) is null when our activity is created for the first time and a new ViewModelStore is created in this case.

//ComponentActivity.java
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}//Activity.java
static final class NonConfigurationInstances {
Object activity; //NonConfigurationInstances(ComponentActivity)
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}

NonConfigurationInstances(Activity.java) is the object which is retained by the Android system even when the activity gets recreated. It has a member named activity which is an instance of NonConfigurationInstances(ComponentActivity.java). This instance contains ViewModelStore.

Note: ViewModels are not retained directly. Instead, ViewModelStore is retained on configuration changes which internally maintains a map of viewmodels.

Let’s deep dive more into this.

//Activity.java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}//ComponentActivity.java
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;
}

Whenever our activity is getting recreated due to any configuration change, onRetainNonConfigurationInstance() gets invoked which returns the NonConfigurationInstances(ComponentActivity.java) instance. This object is retained by the Android system so that it can be delivered to the next activity instance on recreation.

Similarly, we can also retain our own custom objects by implementing the onRetainCustomNonConfigurationInstance().

After the recreation of our activity, NonConfigurationInstances(Activity.java) is received in the attach() method of the Activity class.

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken)

This is how the viewmodels are retained on configuration changes.

Thank You!!!

--

--