A ViewModel to keep them all

Jorge Nicolás Nogueiras
6 min readAug 15, 2020

--

If you thought you couldn’t squeeze your ViewModels any more, here we will talk about how and what else you can do with them using SavedState.

The last few years have been, for all Android developers, endless emotions. The advances that have been made, the number of new libraries, improvements, and changes not only appreciative but also with performance in the platform have excited everyone. Among all these advances and improvements, the incorporation and classification of libraries within Android Jetpack are, in my opinion, the best.

Although these libraries continue to advance and some others are being incorporated — such as Hilt — , today we are going to concentrate on one in particular, the ViewModels. These, in combination with LiveData, give us a secure framework to implement — and why not migrate — our architecture to MVVM.

The particularity of the ViewModels as well as the recently released Hilt, has to do with what Google is betting for the present and the future of Android applications. They are recommending and suggesting to us that we use them, something that had never happened before and was a constant on other platforms.

Much has been said about the use of ViewModels, good and bad practices, their applications, recommendations, etc. However, there is one more thing we can do with them to extend their functionality and that is, keep information even when Android kills the process while our app is in the background; generally caused by low memory.

SavedState

It is already known that ViewModels have been designed to maintain the state of the UI when configuration changes occur. This avoids us having to make extra requests for information that we already had available, making the user wait, overloading our APIs, etc. Additionally, we have the callback onSavedInstanceState in our activities/fragments that allow us to save information that can be recovered even sometime after it has been destroyed by the system.

Perfect, it’s already solved. So why do we need SavedState? Well, let’s say that if we are going to persist some light-weight information that we need to survive the possible destruction of the Activity, it would be ideal to have everything “in one place”, so we don't have problems when it comes the time to look for it. Although the information “retention” cycles are different if we talk about LiveData’s and onSaveInstanceState, by having them separated we are delegating the responsibility of keeping our views up to date in both the ViewModel and the activities/fragments alike.

SavedState comes in a way to cover this need to unify responsibilities. Let’s see what this is about.

Configuration

In order to work with SavedState, we need to include the dependency on Fragments or Activities from androidx.

Androidx's fragment dependencies

In this case, we are going to use the dependencies of Fragment, but we could have used Activities' one and it would be the same.

For older versions of these dependencies, you need to include the lifecycle-viewmodel-savedstate dependencies.

Once the dependency is included, the ViewModels will have the possibility of receiving as a parameter a SavedStateHandle object that will be in charge of saving and returning information even in those cases where our process was terminated by the system.

We can make use of the viewModels delegate looking like this:

viewModels() delegate

Our final ViewModel will be like this:

MainViewModel complete implementation
We send the recycler scroll to the ViewModel

If we pay attention to our constructor, we are receiving the state object of type SavedStateHanlde. Through this, we can save and then access the data that we save there. Although we are saving an object of type Serializable, we can save other types of data too.

One particularity of this object is that it allows us to obtain a value that we have previously saved through a LiveData. In the previous snippet, we can see how we recover the scroll position of a RecyclerView in case our process dies.

Each of the views is capable of saving its own data in case of configuration changes, they can check that their recyclers and any other view maintains their information. Basically every view that contains an id implements its own onSaveInstanceState.

We said that our object survives the process finalization by Android. Let’s see how to validate it using the project that I will leave at the end of the article.

Once the application is installed on our device (or using an emulator), we are going to recreate the process of destroying our Activity, so when recreating we should remain in the same state:

  1. We open the example app
  2. We scrolled “a little”
  3. We send the app to the background.
  4. We search for the app using adb and kill it.
  5. Upon returning to the app, we check that indeed our app was left with the recycler in the position where we left it.

To search for our app in the device processes we do adb shell ps -A | grep com.my-package and to end the process adb shell am kill com.my-package

If we have the developer options enabled on our devices, we can configure the Don’t keep activities option to emulate killing the process in case of changing Activity or sending it to the background.

Wow!

Wait a minute… my ViewModels use Interactors

Even if you are working with a particular architecture for your entire application, such as Clean Architecture or IDD, or you are doing unit testing on your ViewModels, it is necessary to inject other objects (dependencies) into them, so the use of the delegate the way we are already using it is not enough.

What we need is to tell our delegate what is the factory with which it has to build our ViewModel and in order to make use of SavedState, we have to extend the AbstractSavedStateViewModelFactory. In this way, we can build our ViewModel with all its dependencies and add the SavedStateHandle .

Example about using a custom factory for ViewModels and SavedState

From here we can identify some objects that are worth mentioning, to understand how the objective of preserving our data is achieved:

  • SavedStateRegistryOwner: Basically what we need is some class that can handle a SavedStateRegistry. In our case, we have provided this because Fragment implements this interface.
  • AbstractSavedStateViewModelFactory receives a second parameter that we have set as null (optional actually) and allows us, in case of no previous state, to provide a Bundle with info by default.

Real-world use cases

This implementation that we present is a mere example and perhaps not very useful and even trivial. The recyclers, like all the views to which we have indicated an id (identifier), are capable of saving their own state. However, SavedState can be a solution when it comes to avoiding crashes in complex flows, which require transitions between several fragments, such as login, purchase, survey loading, etc; where our app can go to the background, be destroyed by Android and when we return, we no longer have that necessary information that we thought we had secured, causing crashes in production, usability problems and, bad user experience which is even worse.

Conclusion

The ViewModel has been one of those architectural patterns that have come to stay for good (or at least for a while). They have managed to combine the MVVM pattern and the Android life-cycle in such a way that, when used properly, they allow us to obtain good results safely. SavedState as a compliment is simply more useful even for those occasions and contexts in which we need to go further, unifying responsibilities and ensuring that no matter what happens with our application in the background, the information will still be there.
Go ahead, give it a try!

You can find all the code used in the following repository:

Stay in touch, @nogueirasjn at 🐦.
Happy coding!

--

--

Jorge Nicolás Nogueiras
Jorge Nicolás Nogueiras

Written by Jorge Nicolás Nogueiras

Android Engineer | Principal Engineer @ Etermax

No responses yet