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:
ViewModel
-
Lifecycle Awareness:
ViewModel
Lifecycle
-
Data Sharing:
Data can be easily shared between fragments in an activity using
ViewModels
-
Kotlin-Coroutines support:
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
Viewmodel
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!!!