Image for post
Image for post
Illustration by Virginia Poltrack

ViewModels with Saved State, Jetpack Navigation, Data Binding and Coroutines

Lyla Fujiwara
Jun 24, 2019 · 9 min read

Since its introduction, ViewModel has become one of the most “core” Android Jetpack libraries. Based on our 2019 Developer Benchmarking data, over 40% of Android Developers have added ViewModels to their apps. If you’re not familiar with ViewModels, the reason why this is the case might not be clear: ViewModels promote better architecture by separating the data from your UI, making it easy to handle UI lifecycles while also improving testability. For a full explanation check out ViewModels: A Simple Example and the official documentation.

Because ViewModels are so fundamental, there’s been a lot of work over the past couple years to make them easier to use and easier to integrate with other libraries. In this article, I’ll go over four integrations:

  1. Saved State in ViewModels — ViewModel data that survives background process restart
  2. NavGraph with a ViewModel — ViewModels and Navigation library integration
  3. Using ViewModels in data-binding — Easy data-binding with ViewModels and LiveData
  4. viewModelScope — Kotlin Coroutines and ViewModels integration

Saved State in ViewModels : ViewModel data that survives background process restart

Added in lifecycle-viewmodel-savedstate:1.0.0-alpha01
Both Java and Kotlin

The Challenge of onSaveInstanceState

When ViewModels initially launched, there was a confusing issue involving onSaveInstanceState. Activities and fragments can be destroyed in three ways:

1. You meant to navigate away permanently: The user navigates away or explicitly closes the activity — say by pushing the back button or triggering some code that calls finish(). The activity is permanently gone.

2. There is a configuration change: The user rotates the device or does some other configuration change. The activity needs to be immediately rebuilt.

3. The app is put in the background and its process is killed: This happens when the device is running low on memory and needs to quickly free some up. When the user navigates back to your app, the activity will need to be rebuilt.

In situations 2 and 3, you want to rebuild the activity. ViewModels have always helped handle situation 2 for you, because the ViewModel is not destroyed on a configuration change; but in situation 3, the ViewModel is destroyed as well, so you actually need to save and restore data using the onSaveInstanceState callback in your activity. I go into this tricky distinction in much greater detail in ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders.

Saved State Module

The ViewModel Saved State module helps you handle that third situation: process death. The ViewModel no longer needs to send and receive state to and from the activity. Instead you now can handle saving and restoring data all within the ViewModel. The ViewModel can now really handle and hold all of its own data.

This is done using a SavedStateHandle, which is very similar to a Bundle; it’s a key-value map of data. This SavedStateHandle “bundle” is in the ViewModel and it survives background process death. Any data you had to save before in onSaveInstanceState can now be saved in the SavedStateHandle. For example, the id of a user is something you might store in the SavedStateHandle.

Setting up Saved State Module

Let’s see how to use the new module. Note that the code shown below is very similar to this code from Step 6 of the Lifecycles Codelab. That code is in Java, the code below is in Kotlin.

Step 1: Add the Dependency

SavedStateHandle is currently in alpha (meaning the API might change and we’re looking for feedback), and it’s a separate library. The dependency to add is:

implementation ‘androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01’

Note, if you want to keep up with what changes happen in the library, check out the lifecycle release notes page.

Step 2: Update the call to ViewModelProvider

Next, you want to create a type of ViewModel which has a SavedStateHandle. In your activity or fragment’s onCreate, update your call to ViewModelProvider to:

The class that creates ViewModel is a ViewModel factory and there’s a ViewModel factory that makes ViewModels with SavedStateHandles called SavedStateVMFactory. The created ViewModel will now have a SavedStateHandle, associated with the activity/fragment passed in.

Note: The upcoming alpha release of the Androidx activity and fragment libraries will launch in July. In these releases (as noted here), SavedStateVMFactory will become the default ViewModelProvider.Factory when you make a ViewModel in an activity or fragment. This means that if you’re using the newest alpha versions of Androidx activity or fragment, you will not need to add the lifecycle-viewmodel-savedstate dependency or use SavedStateVMFactory explicitly. In short, when this change happens and if you’re using the new alpha versions, you can skip steps 1 and 2, and just go to step 3 below.

Step 3: Use SaveStateHandle in ViewModel

Once you’ve done this, you can use the SavedStateHandle in your ViewModel. Here is an example of keeping a user id in the SavedStateHandle:

  1. Construct: MyViewModel takes in SavedStateHandle as a constructor parameter.
  2. Save: The saveNewUser method shows an example of saving data in a SavedStateHandle. You save the key value pair of USER_KEY and then the current userId. As data updates in the ViewModel, it should be saved in the SavedStateHandle.
  3. Retrieve: savedStateHandle.get(USER_KEY) is an example of getting the current value saved in the SaveStateHandle.

Now if either the activity is destroyed due to rotation or due to the OS killing your process to free up memory, you can be ensured the SavedStateHandle will have your data.

Usually you will use LiveData in your ViewModel. For that you can use the SavedStateHandle.getLiveData() method. Here’s an example of replacing getCurrentUser with a LiveData, which allows for observation:

To learn more check out Step 6 of the Lifecycles Codelab and the official documentation.

ViewModel and Jetpack Navigation : NavGraph with a ViewModel

Added in navigation 2.1.0-alpha02
Both Java and Kotlin

The Challenge of ViewModel Sharing

Jetpack Navigation works out of the box with apps that are designed with relatively few activities — or even just one — containing multiple fragments. Some of the reasons for why we picked this architecture are covered in Ian Lake’s excellent talk Single Activity: Why, When and How. One reason in particular is that this architecture allows you to share data between different destinations by creating an activity-shared ViewModel. You create a ViewModel using the activity, and then you can get a reference to that ViewModel from any fragment that’s part of the activity:

Now imagine that we have a single activity app, and we have eight fragment destinations. Of these, four of them are a shopping checkout flow:

Image for post
Image for post
Navigation Graph with some screens that are in a shopping checkout flow

It’s important for these four destinations in the checkout flow to share data, like the shipping address or whether the user used a coupon code. We’ll put this information in a ViewModel, but a ViewModel associated with what? This information isn’t important to the rest of the app, but previously our only option for a shared ViewModel was to associate the ViewModel to the activity. This means that all of the eight destinations would have access to this ViewModel.

ViewModel NavGraph Integration

Navigation 2.1.0 introduces ViewModels associated to a Navigation Graph. In practice, this means you can take a collection of associated destinations, such as an onboarding flow, a login flow, or a checkout flow; put them into a nested navigation graph; and enable shared data just between those screens.

To create a nested navigation graph, you can select your screens, right click, and select Move to Nested Graph → New Graph:

Image for post
Image for post
Screenshot showing how to “Move to Nested Graph”

In the XML view, note the id of the nested navigation graph, in this case checkout_graph:

Once you’ve done this, you get the ViewModel using by navGraphViewModels:

This also works in the Java programming language, using:

Note that a nested graph is encapsulated from the rest of the navigation graph. You can navigate to a nested graph (you’ll go to the start destination of the nested graph), but you cannot navigate directly to a particular destination within the nested graph from outside of the graph. Thus they are meant for encapsulated collections of screens, like a checkout flow or a login flow.

The ViewModel NavGraph integration was one of the new navigation features announced at I/O 2019. For more, check out the talk Jetpack Navigation and the documentation.

ViewModel and Data Binding : Use your ViewModel and LiveData in Data Binding

Added in Android Studio 3.1
Both Java and Kotlin

All that LiveData boilerplate

This integration is an oldie but a goodie. ViewModels usually contain LiveData, and LiveData is meant to be observed. Usually this means adding an observer in fragment:

The Data Binding library is all about observing your data and updating the UI. By using ViewModel, LiveData and Data Binding together, you can remove the previous LiveData observation code and reference your ViewModel and LiveData straight from the layout XML.

Using Data Binding, ViewModel and LiveData

Let’s say in your XML layout you want to reference your ViewModel:

To use LiveData with Data Binding, you just need to call binding.setLifecycleOwner(this) and then pass your ViewModel to your binding, like so:

Now in your layout, you can use your ViewModel. As seen below, I set the text to viewmodel.name:

Note that viewmodel.name could be a String or a LiveData. If it’s a LiveData, the UI will update whenever the LiveData changes.

ViewModel and Kotlin Coroutines : viewModelScope

Added in Lifecycle 2.1.0
Kotlin only

Coroutines on Android

Kotlin Coroutines are a new way to handle asynchronous code. Another way to handle asynchronous code is to use callbacks. Callbacks are fine, but if you’re writing complicated asynchronous code, you can end up with many levels of nested callbacks; this makes your code hard to understand. Coroutines simplify all of this, and also provide an easy way to make sure you’re not blocking the main thread. If you’re new to coroutines, there’s a great in-depth blog-post series called Coroutines on Android and the codelab Using Kotlin Coroutines in your Android App.

A simple coroutine looks like a block of code that does some work:

Here I’m only starting one coroutine, but It’s easy to start hundreds of coroutines and potentially lose track of them — if you have lost track of a coroutine and it’s running some work you intended to stop, it is known as a work leak.

To avoid work leaks you should organize your coroutines by adding them to a CoroutineScope, which is an object that keeps track of coroutines. CoroutineScopes can be cancelled; and when you cancel a scope, they cancel all the associated coroutines. Above I’m using the GlobalScope, which is, as the name implies, a CoroutineScope that is available globally. It’s generally not good practice to use the GlobalScope for the same reasons it’s generally not good to write globally accessible variables. So you’ll need to either make a scope, or get access to one. In ViewModels, this is easy if you use viewModelScope.

viewModelScope

Often if your ViewModel is destroyed, there’s a bunch of “work” associated with the ViewModel that should be stopped as well.

For example, let’s say you’re preparing a bitmap to show on-screen. That’s an example of work you should do without blocking the main thread and work that should be stopped if you permanently navigate away from or close the screen. For work like this, you should use viewModelScope.

viewModelScope is a Kotlin extension property on the ViewModel class. It is a CoroutineScope that is cancelled once the ViewModel is destroyed (when onCleared() is called). Thus when you’re using a ViewModel, you can start all of your coroutines using this scope.

Here’s a small example:

The excellent blogpost Easy Coroutines in Android: viewModelScope goes into a bunch more detail if you’re using Kotlin Coroutines and ViewModels. For more about coroutines and architecture components, check out the documentation and the talk Understand Kotlin Coroutines on Android.

Conclusion

In summary:

  1. ViewModels handle the onSaveInstanceState case with the SavedStateHandle module.
  2. You can scope a ViewModel to a Jetpack Navigation NavGraph for more precise and encapsulated data sharing between fragments.
  3. If you’re using the Data Binding library and ViewModels, you can pass your ViewModel to your binding. If you’re also using LiveData, use binding.setLifecycleOwner(lifecycleOwner).
  4. …and if you’re using Kotlin Coroutines with ViewModel, then use viewModelScope to cancel your coroutines automatically when the ViewModel is destroyed.

Many of these integrations were from direct feedback and requests from the community. If there’s a ViewModel feature or integration you’re looking for you can follow the list of feature requests and consider making your own request.

To keep up to date on what’s going on with architecture and Android Jetpack, follow the Android Developers Medium blog and keep an eye on the AndroidX release notes.

Questions about any of these features? Leave a comment! Thanks for reading!

Special thanks to Ian Lake, Yigit Boyar, Jose Alcérreca, Sean McQuillan, Jisha Abubaker, and Alex Michael Cook for their revisions and contributions.

Android Developers

The official Android Developers publication on Medium

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store