ViewModels under the hood..
Last year at Google I/O, Google announced a set of libraries named Android Architecture Components.
The Android architecture components were created for the following reasons. (This is how I perceive them and it does not reflect Google or its engineers’ actual opinions):
1 . Give an opinion on how to architect an android app. There are many different ways to architect an android app and many of those were not mentioned in the docs. This confused a lot of new Android developers trying to use only the documentation for learning.
2 . Provide tools to help in solving common problems found by android developers. (lifecycle management, data persistence, paging)
Android Architecture Components is the combination of the libraries: Room, ViewModel, LiveData, Lifecycle aware components and the Paging Library.
Last year, I gave two presentations about these components. These were focused on the ViewModels and the Lifecycle aware components(Link to one of the recordings)
After the presentation, the audience would normally have two feelings. Excitement for finding a solution to solve their problems with ViewModels, and curiosity to understand how they work.
To answer to that curiosity and other common questions such as:
-Why should we not store too much data inside the ViewModel?
- Why can we not use ViewModels to communicate between Activities?
I decided to write this post and explain what happens under the hood when using ViewModels.
This post will not talk about the other components in the library. There are so many resources available and some of the links to these will be posted at the bottom of this post.
ViewModel
The ViewModel class is designed to store and manage UI-related data so that the data survives configuration changes such as screen rotations. — developer.android.com
Suppose you have an application with a screen to show the details of a Movie.
When opened, this screen will display the information loaded from a source (network/database). If the user decides to rotate the device, the information will be reloaded from the source.
This is because the Android system will recreate the Activity on the rotation configuration change.
That small behavior repeated many times, can waste the users’ data if the source is a remote server. This is one very small problem but a lot more damage can be done in more complex cases.
To solve this small problem and make our app a good citizen on the users’ device we could try to do the following:
1. Make the class Movie
a Parcelable
object and save it in the instance state Bundle
.
2. Process configuration changes on our own.
3. Create a ViewModel
, keep the Movie
object in the ViewModel
and let it do the rest.
The first solution is the easier to implement and it is the most used for simple cases like our example. However, it is easy to hit the limit size of a Parcelable
if the objects become too complex.
The second solution would prevent the Activity
from restarting. It would solve our problem but we lose in the process other features that could benefit from the Activity
restart(Reload appropriate resources for the new configuration).
The third solution using the ViewModels
is the most reliable and easier to implement even in the most of the complex cases. To implement this solution we would go through the following steps:
1. Create a subclass of the ViewModel
class that has a Movie
as a variable.
2. Expose a method on the ViewModel
that takes the Id of the movie details it should load.
3. Expose a LiveData
that emits a Movie
object loaded in the ViewModel
.
4. Get an instance of the ViewModel
inside the Activity
/Fragment
.
5. Observe for changes from the exposed LiveData
inside the Activity
/Fragment
.
6.Fetch the Movie
from the source and wait for it to be posted by the LiveData
.
7.Finally, display the movie details when the Movie
object is posted to the Activity
/Fragment
were we are observing from.
Now, if the device is rotated the ViewModel
will survive the configuration change and it will exist as long as the scope it is associated with still exists. As shown in the image below the ViewModel will stay alive until it’s scope(Activity or Fragment) is destroyed.
This means we will get the same ViewModel
instance. We will start observing this instance LiveData
and the latest emitted data before the rotation will be emitted.
Those seven steps are relatively easy to do. But they do not answer the questions I mentioned earlier.
To do that, let’s see how this process happens in details.
The process of creating/getting a ViewModel Instance
Imagine we have a MovieDetailsViewModel
class. We can get its instance by executing this statement from a Fragment/Activity:
ViewModelProviders.of(this).get(MyViewModel.class);
The chained calls in the statement above can be divided into two separate parts:
1st — A call to get a ViewModelProvider
using the static method of
in the ViewModelProviders
class.
2nd — A call to get the ViewModel
from the ViewModelProvider retrieved on the first call.
ViewModelProviders
The ViewModelProviders
is a class with helper methods to get a ViewModelProvider
.
This class has the DefaultFactory
as it’s only private variable. The DefaultFactory
class is used to create a new Instance of a ViewModelProvider
.ViewModelProviders
have an overloading of the static method named of
. One of the methods takes an Activity
and the other a Fragment
as a parameter as shown on the following signatures:
public static ViewModelProvider of(@NonNull FragmentActivity activity)
public static ViewModelProvider of(@NonNull Fragment fragment)
When calling the of
the method of to get a ViewModelProvider
, the following will happen:
- Check and get the Fragment/Activity
Application
.
For anActivity
, this check and retrieval is made by calling thecheckApplication(Activity)
method.
This method, calls theActivity
getApplication()
which will return the application or throw an exception if null.
When calling theof
method passing aFragment
, a similar process is used. In this case, the getApplication parameter is a wrapped call tocheckActivity(Fragment)
. The checkActivity method will get theActivity
for theFragment
. This will ensure theFragment
is attached to anActivity
first. - Initialize the
ViewModelProvider
Factory.
To do this, theinitializeFactoryIfNeeded(Application)
method will be called. This method takes as a parameter theApplication
instance retrieved in the previous step.
3. Return theViewModelProvider
.
Theof
method will create and return a new instance of the ViewModelProvider.
This step complete implementation to get a ViewModelProvider
for an Activity
looks like this:
ViewModelProvider
The ViewModelProvider
, as the name implies is the class that actually provides the ViewModel
.
This class, contains the static method get(Class<T> modelClass)
. This is the method invoked in the second part of the statement to get a ViewModel
mentioned earlier.
From the exposed API, everything is pretty simple. But there is way more happening before we get that instance of a ViewModel
as we are going to see next.
The ViewModelProvider
have two constructors:
1.ViewModelProvider(ViewModelStore store, Factory factory)
2. ViewModelProvider(ViewModelStoreOwner storeOwner, Factory factory)
Both constructors take the Factory
to create a ViewModel
class as the second parameter but differ in their first parameter.
The first constructor takes a ViewModelStore
. A ViewModelStore
is a class that stores ViewModels
.
To save ViewModels
, the ViewModelStore
stores them in a HashMap
. Each ViewModel
is saved in the map using a String
as key.
The key is the concatenation of the DefaultName
variable and the ViewModel
class canonical name. (DefaultName
is a simple final string inside the ViewModelProvider
class)
A ViewModelProvider
created with a ViewModelStore
will return the ViewModel
by doing the following:
1. A key will be created using the DefaultName
and the canonical name of the class passed as a parameter of the get
method.
2. Use the key to retrieve the ViewModel
Instance from the HashMap
.
3. If found, Perform checks and ensure the correct ViewModel
instance is returned. Otherwise, a new instance will be created, saved in the ViewModelStore
, and returned.
So far we know how the ViewModels
are created and stored but,
this doesn’t yet explain the question of how they survive the configuration changes. This is because this first constructor of ViewModelProviders
takes a ViewModelStore
and this store on it’s own does not belong to any scope.
In the implementation to solve the problem we started with, the ViewModelProvider
will be created using the second constructor. The constructor that takes as the first parameter a ViewModelStoreOwner
ViewModelStoreOwner
The ViewModelStoreOwner
is an Interface and as the name implies is the owner of a ViewModelStore
. This can be any class that implements the getViewModelStore()
defined by this interface.
In the library, the ViewModelStoreOwner
is the HolderFragment
class. This class has a ViewModelStore
variable that can be accessed by the getViewModelStore()
method.HolderFragment
is a regular Android Fragment
. This Fragment
uses the setRetainInstanceState(boolean)
method to true in order to retain the instance state. As you can guess, the state has the ViewModelStore
with all the ViewModels
it contains.
This technique has been used for the same purpose so many times in the past before the Android Architecture components.
HolderFragment
is the scope where all ViewModels inside the ViewModelStore
will live. According to the image with the ViewModel
scope shown earlier in the post, All ViewModels will be cleared if the scope(HolderFragment
) is destroyed.
Now we know how the ViewModels
survive configuration changes. However, this doesn’t explain how we can do things such as communicate between Fragments with ViewModels and why we can’t do the same with two Activities.
Who owns the HolderFragment?
HolderFragmentManager
The HolderFragment
has an inner static class named HolderFragmentManager
. The HolderFragmentManager
creates and managesHolderFragment
instances.
After creating the instances it associates them with an Activity
or Fragment
.
The whole process is done using the methods holderFragmentFor(Activity)
and holderFragmentFor(Fragment)
.
These methods will behave in two different ways if there is or not an instance of the HolderFragment
.
For the case where there is no instance of the HolderFragment
, these methods will:
1. Create an instance of HolderFragment
.
2. Add the new instance to the parent(Activity
/Fragment
) FragmentManager
. This will result in the expansion of the scope of the ViewModels inside the HolderFragment
. As a result, the ViewModels are going to be alive as long as the Activity
/Fragment
that has them stored in its FragmentManager
3. Register a callback to the Activity
/Fragment
lifecycle onDestroy()
callback method. BecauseHolderFragment
is inside an Activity
/Fragment
FragmentManager
when the Activity
/Fragment
is destroyed its onDestroy()
method will be called and the ViewModelStore
will be cleared. Lastly, the HolderFragment
instance will be removed from the HolderFragmentManager
HashMap
that associated it with an Activity
/Fragment
when the registered callback is invoked.
4. Add the holder Fragment
to a HashMap
where it’s key is an Activity
/Fragment
. This is why we cannot communicate between two Activities or Fragments using the same ViewModel
.
For Fragments, the communication is possible only if they can have the same parent. This means that we can cheat and get the same instance of a ViewModel
by asking for the parent Activity
instead of the Fragment
itself.
5. Return the HolderFragment instance.
When there is already an instance of the HolderFragment
these methods will look up and return the instance already in the HashMap
.
Knowing the details below the ViewModelStoreOwner
, we can go back to the ViewModelsProviders
to see how we create the ViewModelStoreOnwer
needed to create a ViewModelProvider
instance.
The ViewModelProviders
class gets an instance of the ViewModelStoreOwner
by calling the static methods of
from the ViewModelStores
class.
The ViewModelStores
class job is of abstracting the call to the holderFragmentOf(Activity/Fragment)
static method inside the HolderFragmentManager
. As mentioned before this method will be responsible for returning a ViewModelStoreOwner
.
I hope you had a good time reading this long post and by now you will have a bit more of understanding of how the ViewModels are created and work.
My hope is that you can use the details on this explanation to always remember that:
— You should not try to get an instance of your ViewModel
before your Activity
is created. If getting it for a Fragment
, you should not try to get it on a Fragment
not attached to an Activity
.
— You should not put too many things on your ViewModel
. As you could see they are just a regular Fragment
and if the system decides to kill because it is using too much resources its parent and all your ViewModels will be gone too. In order to be safe from this kind of problems, I advise you to use the ViewModel
alongside the savedInstance state solution.
In our example, storing the movie Id in the instance state would ensure that even if the ViewModel is killed we will still be able to get the correct movie id and pass it to reload the data for the appropriate Movie.
— You can only use ViewModels to communicate between Fragments that share the same Parent.
If you feel like something was not clear, have any suggestions or something to add up, please drop your comments below and let’s get the conversation started.
Thanks for your time and feel free to share with a fellow dev you think would enjoy reading this post.
See you next time!
Useful links:
Architecture components official guide
Architecture components Intro — Google IO 17
Architecture components — GDD Europe 2017(Florina Muntenescu)
Android Architecture Components — Looking at ViewModels — PART 2 (Rebecca Franks)
Intro to Android Architecture components — SSA Experts on Air Episode 6
Thanks to Florina Muntenescu, Rosário Pereira Fernandes and Mustafa Ali for reviewing the first version of the post.