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.
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
Parcelable object and save it in the instance state
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
4. Get an instance of the
ViewModel inside the
5. Observe for changes from the exposed
LiveData inside the
Movie from the source and wait for it to be posted by the
7.Finally, display the movie details when the
Movie object is posted to the
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:
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
2nd — A call to get the
ViewModel from the ViewModelProvider retrieved on the first call.
ViewModelProviders is a class with helper methods to get a
This class has the
DefaultFactory as it’s only private variable. The
DefaultFactory class is used to create a new Instance of a
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
Activity, this check and retrieval is made by calling the
This method, calls the
getApplication()which will return the application or throw an exception if null.
When calling the
ofmethod passing a
Fragment, a similar process is used. In this case, the getApplication parameter is a wrapped call to
checkActivity(Fragment). The checkActivity method will get the
Fragment. This will ensure the
Fragmentis attached to an
- Initialize the
To do this, the
initializeFactoryIfNeeded(Application)method will be called. This method takes as a parameter the
Applicationinstance retrieved in the previous step.
3. Return the
ofmethod will create and return a new instance of the ViewModelProvider.
This step complete implementation to get a
ViewModelProvider for an
Activity looks like this:
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.
ViewModelProvider have two constructors:
ViewModelProvider(ViewModelStore store, Factory factory)
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 is a class that stores
ViewModelStore stores them in a
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 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
2. Use the key to retrieve the
ViewModel Instance from the
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 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
HolderFragment is a regular Android
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?
HolderFragment has an inner static class named
HolderFragmentManager creates and manages
After creating the instances it associates them with an
The whole process is done using the methods
These methods will behave in two different ways if there is or not an instance of the
For the case where there is no instance of the
HolderFragment, these methods will:
1. Create an instance of
2. Add the new instance to the parent(
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
Fragment that has them stored in its
3. Register a callback to the
onDestroy() callback method. Because
HolderFragment is inside an
FragmentManager when the
Fragment is destroyed its
onDestroy() method will be called and the
ViewModelStore will be cleared. Lastly, the
HolderFragment instance will be removed from the
HashMap that associated it with an
Fragment when the registered callback is invoked.
4. Add the holder
Fragment to a
HashMap where it’s key is an
Fragment. This is why we cannot communicate between two Activities or Fragments using the same
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
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
Knowing the details below the
ViewModelStoreOwner, we can go back to the
ViewModelsProviders to see how we create the
ViewModelStoreOnwer needed to create a
ViewModelProviders class gets an instance of the
ViewModelStoreOwner by calling the static methods
of from 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
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
— 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!
Intro to Android Architecture components — SSA Experts on Air Episode 6