Android ViewModels: Under the hood

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
class also helps in implementing MVVM(Model-View-ViewModel) design pattern which is also the recommended Android app architecture for Android applications by Google.ViewModel
Also, there are other various advantages of using ViewModel
class provided by Android framework like:
- Handle configuration changes:
objects are automatically retained whenever activity is recreated due to configuration changes.ViewModel
- Lifecycle Awareness:
objects are also lifecycle-aware. They are automatically cleared when theViewModel
they are observing gets permanently destroyed.Lifecycle
- Data Sharing: Data can be easily shared between fragments in an activity using
.ViewModels
- Kotlin-Coroutines support:
includes support for Kotlin-Coroutines. So, they can be easily integrated for any asynchronous processing.ViewModel
How ViewModel work internally?
This is the sample project we are going to use for explaining how the
retains itself on configuration changes.viewmodel
This is a very simple application with a single activity named
which shows a counter. The counter value is our view state kept inside the MainActivity
using a CounterViewModel
object. The activity also shows the LiveData
of the current activity instance.hashcode
On any configuration change, the current activity instance is destroyed and a new instance of the activity is created which makes the
to change. But the counter value is retained as we are keeping it inside our hashcode
and the ViewModel
are not destroyed if the activity is getting recreated due to configuration changes.viewmodels
The first thought around how
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 viewmodels
in case of fragments).FragmentManager
So let’s discuss how this magic happens and how our
instance is retained even if the activity is recreated.viewmodel
viewModel = ViewModelProvider(this, ViewModelFactory()).get(CounterViewModel::class.java)
This is the code to get a
instance in an activity. As we can see, we are getting an instance of viewmodel
by passing two arguments, our activity instance and an instance of ViewModelProvider
. Then, we are using this ViewModelFactory
instance to get our ViewModelProvider
object.CounterViewModel
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:
- Creation of
ViewModelProvider
- Getting the instance of
fromViewmodel
ViewModelProvider
Creation of ViewModelProvider
ViewModelProvider
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
The constructor of
takes two parameters, ViewModelProvider
and ViewModelStoreOwner
. In our example, the activity is the Factory
and we are passing our own custom factory. ViewModelStoreOwner
is a simple interface with a single method named ViewModelStoreOwner
getViewModelStore()
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
In the constructor, we are simply getting the
from ViewModelStore
and passing it to the other constructor where they are just assigned to the respective class members.ViewModelStoreOwner
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
So now we know that our activity is the
and its the responsibility of our activity to provide the ViewModelStoreOwner
.ViewModelStore
Getting the ViewModel
from ViewModelProvider
ViewModel
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
method on our get()
, it gets the canonical name of the view model class and creates a key by appending the canonical name to a DEFAULT_KEY.ViewModelProvider
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
(which is provided by our activity) whether a ViewModelStore
instance for the given key is already present or not. If the viewmodel
is already present, it simply returns the viewmodel
instance present in viewmodel
and if it’s not present, the ViewModelStore
uses the ViewModelProvider
instance to create a new Factory
object and also stores it in viewmodel
.ViewModelStore
Now we know that our activity is responsible for storing the ViewModel instances. But it’s still a mystery that how these
instances are retained even when the activity instance is recreated on configuration change.ViewModel
How ViewModel retain itself?
As we saw earlier when we created
we were passing an instance of ViewModelProvider
i.e., our ViewModelStoreOwner
instance.activity
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner
Our activity implements the
the interface which has a single method named ViewModelStoreOwner
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
returns the getViewModelStore()
. When our activity is recreated due to any configuration change, mViewModelStore
contains the previous instance of nc(NonConfigurationInstances)
. ViewModelStore
is null when our activity is created for the first time and a new nc(NonConfigurationInstances)
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;
}
is the object which is retained by the Android system even when the activity gets recreated. It has a member named NonConfigurationInstances(Activity.java)
which is an instance of activity
. This instance contains NonConfigurationInstances(ComponentActivity.java)
.ViewModelStore
Note:are not retained directly. Instead,
ViewModels
is retained on configuration changes which internally maintains a map of
ViewModelStore
.
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,
gets invoked which returns the onRetainNonConfigurationInstance()
instance. This object is retained by the Android system so that it can be delivered to the next activity instance on recreation.NonConfigurationInstances(ComponentActivity.java)
Similarly, we can also retain our own custom objects by implementing the
.onRetainCustomNonConfigurationInstance()
After the recreation of our activity,
is received in the NonConfigurationInstances(Activity.java)
method of the attach(
)
class.Activity
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
are retained on configuration changes.viewmodels
You can also connect with me on LinkedIn, Twitter, Facebook and Github.
Thank You!!!