Internals of Android Architecture Components Part I — The ViewModel
You can see the other posts here:
Why should we understand how AAC works?
First of all, let’s cover the motivation for this series. I consider best practice to decouple the logic of your application from the platform it runs upon (see Ports and Adapters), that involves decoupling it from any API it may consume, the storage solutions (SQLite, Realm, SharedPreferences, the file system,…), and more importantly, the Android runtime.
Some of the reasons to it is that:
- Platform dependencies will hamper the testability of your solution, making it either harder to unit test, slower, or impossible.
- The platform dependencies will evolve over time, outside of your control. A change on these dependencies, should not imply a change on your business logic. This is driven by the Single Responsibility Principle (a class should have a single reason to change), and the Stable Dependencies Principle (a package should depend on packages less likely to change than itself).
With AAC, Google is offering a set of libraries that drive Architectural decisions that target the business logic of your app, but at the same time, coupling it onto the Android SDK.
What are the AAC ViewModels?
On Android’s documentation page it states that
ViewModels can be used to keep …
“Store UI-related data that isn’t destroyed on app rotations.”
But how does it manage to do so? Let’s reveal the magic tricks.
How AAC ViewModels are retained across configuration changes
First, let’s visit the Android documentation to see how the
ViewModel is consumed. The following code is the example of how a
Fragment uses the SDK in order to provide a
ViewModel that is retained on configuration changes:
The line we want to focus on right now is:
we can infer that it’s retrieving a
ViewModel of type
MyViewModel. Let’s look into how each of these methods work, starting with
It looks like
ViewModelProviders.of is just a factory of
ViewModelProvider, who depends upon a
ViewModelStore and a
ViewModelFactory. We’ll have to dig deeper to fully understand how
If we look into the dependencies, the
ViewModelStore 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:
On the other hand, the
ViewModelFactory is using reflection to instantiate the
ViewModel we need. The
AndroidViewModelFactory used in
ViewModelProviders.of overrides a generic
ViewModelFactory in order to provide a reference to the Application
Context to the
ViewModel class is of type
AndroidViewModel, then it will create a new instance passing the application as a single parameter in the constructor, otherwise, it calls the parent implementation, which is:
This is a simple factory implemented via reflection that provides no arguments to the
How it all ties together
Now that we understand the
ViewModelProvider creation and its dependencies, we will look into how it creates and retrieves the
ViewModel instances, retaining them throughout configuration changes. Let’s look into the
get(MyViewModel.class) method call.
This works as one would expect, it tries to retrieve a
ViewModel from the store, if none is there, it uses the factory to create it and stores it. In order to retrieve the already created
ViewModel, it generates a key from the class qualified name.
Surviving configuration changes
So far we’ve seen how the
ViewModelStore is the object responsible to keep the references of
ViewModel to be reused, but how is the
ViewModelStore surviving configuration changes itself? Let’s go back to the implementation of
ViewModelStores.of seems to be a similar method to
ViewModelProviders.of, creating new instances of the
ViewModelStore as required. Let’s look into how this is implemented:
Apparently, we can provide the
ViewModelStore in three ways:
- Using the
Fragment, and owning the responsibility of creating and disposing the store.
- Let AAC create a
HolderFragment, who already implements
ViewModelStoreOwner, and the AAC library and Android SDK will do the heavy lifting.
How HolderFragment retains the state
This raises the question of what is a
HolderFragment and how it maps to our
Fragment stack. Let’s look now at how
HolderFragment has a couple of ways of looking for the
HolderFragment associated with your
Fragment, and if not found, it will create new
HolderFragment and add it on the
FragmentManager of our own
Fragment. Now, how does this fragment in our stack survive rotation changes while the rest of the fragments die in their regular lifecycle? The answer is in the constructor:
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.
android.support.v4.app.Fragment retains state
On the other hand, if you opt for using the support
Fragment, the state will be retained, however it becomes more complex to follow the code that manages it.
In order to understand the flow of control of this classes, we looked at the
onDestroy method of the Fragment:
The condition to call clear on the ViewModelStore is that
mHost.mFragmentManager.mStateSaved is false.
By adding a breakpoint on the
mStateSaved instance variable we managed to identify when it is set to true, and by whom:
By navigating the resulting stack trace, we end up on discovering that the
FragmentManager.saveAllState method is called by
To summarise, the reasons it is able to create and retain the
ViewModelProvidercreates with Reflection the
ViewModelacross configuration changes with a
ViewModelStore, provided by a
ViewModelStoreOwner, this can be done with a
android.support.v4.app.Fragmentor implementing our own.
android.support.v4.app.Fragmentwill use the
FragmentManager.saveAllStateas called by
FragmentActivity.onSaveInstanceStateto retain the
HolderFragmentis a headless
Fragment(without UI) that is added to the Fragment stack with
Other sources that go into more depth onto how AAC works in depth are:
Last year at Google I/O, Google announced a set of libraries named Android Architecture Components.medium.com