Dive deep into Android’s ViewModel — Android Architecture Components
Brief overview of Android Architecture Components
Architecture components are part of Android Jetpack that persist data, manage lifecycle, make your app modular, help you avoid memory leaks, and prevent you from having to write boring boilerplate code.
- ViewModel stores UI-related data that isn’t destroyed on app rotations.
- Lifecycle-Aware Components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments.
- LiveData is an observable data holder. It notifies observers when data changes so that you can update the UI. It is also lifecycle aware.
- Room is a robust SQLite object mapping library. Using it avoids boilerplate code and easily convert SQLite table data to Java objects.
Introduction
Most of us have would have faced a issue — while we rotate the phone, the UI looses it’s state or in some cases even the app crashes.
The quickest way to handle this would be :
- Saving the data into bundle during onSaveInstanceState and retaining the same data within onCreate, but in case of complex data it is easy to hit the limit size of a Parcelable.
- Process configuration changes on our own.
- Fixing the Activity orientation to portrait mode, but it wouldn’t be correct in case the application needs to run both in portrait as well as landscape mode.
During Google I/O 2017, Android Team released ViewModel during Google I/O 2017 that can handle such scenarios.
What is ViewModel?
As per Android’s Official documentation, ViewModel class is designed to
Store and manage UI-related data in a lifecycle conscious way. It allows data to survive configuration changes such as screen rotations.
A ViewModel is always created in association with a scope (a fragment or an activity) and will be retained until its associated Activity or Fragment is disposed of forever — that means view data survives events like a Fragment / Activity being recreated due to rotation.
Mainly a ViewModel is responsible for : —
- preparing and managing the data for an Activity or a Fragment.
- retains the data during configuration changes, e.g. — Rotation
- handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
ViewModel separates ownership of view data and logic from lifecycle-bound entities like Activities and Fragments. ViewModels not only eliminate common lifecycle issues, they also help build UIs that are more modular and easier to test.
The Activity or the Fragment are able to observe changes in the ViewModel via LiveData or Android Data Binding.
Note : — ViewModel’s only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.
The following diagram shows the life cycle of ViewModel component.
How does a ViewModel survive configuration changes?
First, let’s visit the Android documentation to see how the ViewModel is consumed. The following code is the example of how an Activity uses the SDK in order to provide a ViewModel that is retained on configuration changes.
Get the instance of ViewModel
Writing your ViewModel class
ViewModelProviders.of(this).get(MyViewModel.class);
From the above snippet, we can infer that it’s retrieving a ViewModel of type MyViewModel.
Let’s dig deeper into how each of these classes and it’s methods, starting with ViewModelProviders.of :
It seems like ViewModelProviders.of is just a factory of ViewModelProvider, which depends upon ViewModelFactory and a ViewModelStore.
ViewModelFactory
It is a class which uses reflection to instantiate the ViewModel class which was passed to ViewModelProviders.of(this).get(MyViewModel.class).
If the ViewModel class is of type AndroidViewModel, then it will create a new instance passing the Application instance as a single parameter in the constructor.
Creating a custom ViewModelProviderFactory
You can also create a custom Factory class that overrides a generic ViewModelFactory to initialize your ViewModel class and passing the data (if needed) to initialize your ViewModel class.
The factory created above can then be passed to ViewModelProviders class while requesting the ViewModel instance
//initializing the custom factory class
MyViewModelFactory factory = new MyViewModelFactory(data1, data2);ViewModelProviders.of(this, factory).get(MyViewModel.class);
Note: — If the
ViewModel
class is of typeAndroidViewModel
, then it will create a new instance passing the application as a single parameter in the constructor, otherwise, it calls the parent implementation.
ViewModelStores
This class job is of abstracting the call to the holderFragmentOf (Activity/Fragment) static method inside the HolderFragmentManager. This method will be responsible for returning a ViewModelStoreOwner.
ViewModelStores.of seems to be similar method to ViewModelProviders.of, creating new instances of the ViewModelStore when required.
ViewModelStore
It seems to be a simple store with a HashMap<String, ViewModel>, where the key is the classname of the view model and the Object the ViewModel itself.
ViewModelStoreOwner
It is an Functional 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
HolderFragment is a regular Android Headless Fragment. It is the scope where all ViewModels inside the ViewModelStore will live.
Who owns the HolderFragment?
HolderFragmentManager
The HolderFragment has an inner static class named HolderFragmentManager. The HolderFragmentManager creates and manages HolderFragment 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 when there is no instance of the HolderFragment, these methods will:
- Create an instance of HolderFragment.
- 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 .
- Register a callback to the Activity/Fragment lifecycle onDestroy() callback method. BecauseHolderFragment is inside an Activity/Fragment FragmentManager, so 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.
- Add the holder Fragment to a HashMap where it’s key is an Activity/Fragment.
- 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.
How does HolderFragment retains the state ?
By setting retain instance to true and not providing a view, the HolderFragment becomes essentially a headless Fragment that is retained for as long as the Activity is not destroyed.
public HolderFragment() {
setRetainInstance(true);
}
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
- onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
- onCreate(Bundle) will not be called since the fragment is not being re-created.
- onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Retrieving ViewModel instance
Since we have a basic understanding of ViewModelProvider creation and its dependencies, we will now look into how it creates and retrieves theViewModel instances, retaining them throughout configuration changes. Let’s look into the method call : —
get(MyViewModel.class)
It tries to retrieve a MyViewModel instance from the store. If none is there, it uses the factory to create it and then stores it into HashMap<String, ViewModel>. In order to retrieve the already created ViewModel, it generates a key from the class qualified name.
Summary
In this post, I explored the very basics of the new ViewModel class. The key takeaways are:
- The ViewModel class is designed to hold and manage UI-related data in a life-cycle conscious way. This allows data to survive configuration changes such as screen rotations.
- ViewModels separate UI implementation from your app’s data.
- The lifecycle of a ViewModel extends since the time when associated UI controller is first created, till it is completely destroyed.
- Never store a UI controller or Context directly or indirectly in a ViewModel. This includes storing a View in a ViewModel. Direct or indirect references to UI controllers defeat the purpose of separating the UI from the data and can lead to memory leaks.
- A HolderFragment is a headless Fragment (without UI) that is added to the Fragment stack with setRetainInstance(true).
If you feel like something was not clear, have any suggestions or something to add up, please drop your comments below.