Jetpack Saved State for ViewModel: Getting Started

In this tutorial, you’ll learn how to used the Saved State library from Android Jetpack to preserve the UI state of an Android application. By Ishan Khanna.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Simulating Process Death

For this step, you must use an emulator with Android version P or above.

Finding the App Process

First, make sure the app is running. If it isn’t, click the Run button on top of Android Studio. Then, in your terminal type the following command and hit Enter.

adb shell ps -A

This executes a process status shell command on your device and prints a list of processes running. You added -A to list all processes.

It’s crazy to search through the list of all these processes. So, here’s a neat trick to help you do this effortlessly.

grep a part of your app’s Package Name and it’ll narrow down the results you have to skim through.

For example:

adb shell ps -A | grep hopelesshubby

The output of this command confirms your app’s process is running and should look something like this:

Find the running process

In the next section, you’ll simulate the app-killing process.

Killing the App Process

First, press the Home button on your device or emulator. Then run this command in your terminal:

adb shell am kill com.raywenderlich.hopelesshubby

Next, confirm that you successfully killed the process. Copy, paste and run this command:

$ adb shell ps -A | grep hopelesshubby

You should get nothing as a result. This means you killed the process correctly.

Feeling the User Experience

So far, you’ve learned how to find running processes and kill them using the ADB. Next, you’ll run the app again to see how this disrupts user experience in real life.

To get a feel for how this affects a user, run the app again. You can do this from the device by clicking on the app icon or from Android Studio by pressing the Run Button.

Running Hopeless Hubby

Next, add a couple of items to the shopping list.

First, click on the EditText and then add the name of your item. Then, click the ADD button.

The items should appear on the list below, like this:

Added items to Hopeless Hubby

Next, click the Home button and run this command in your terminal:

adb shell am kill com.raywenderlich.hopelesshubby

After the app starts, do you notice the items you entered are gone?

Frustrating, isn’t it? That’s what happens to your users as well. So, how about implementing a solution to this problem?

Handling The Process Death

By using the onSaveInstanceState() callback you can handle the system-initiated process death gracefully and provide your users a delightful experience.

If the system kills and later recreates an Activity or a Fragment, then the callback above comes to the rescue by persisting the data needed to restore the UI State.

You’ll solve this problem in the next section by introducing the Saved State library from Android Jetpack.

Adding the Saved State Library

Time to add the ViewModel Saved State Library into your app.

First, open build.gradle in your app module.

Next, add this line to your dependencies lists:

implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha02'

With the dependency added, next, you will add the code needed to load a ViewModel‘s state.

Retrieving The ViewModel State With SavedStateHandle

Next, open ShoppingListViewModel.kt and modify its constructor by adding the following object to it:

val savedStateHandle: SavedStateHandle

The SavedStateHandle you’ve added persists data using a key-value type interface, similar to using a Bundle or a Map. Therefore, you need to define a key with which you can save and restore the data. Add the following key to your ShoppingListViewModel class:

private val KEY = "Saved_Shopping_List"

Next, you need to initialize your items LiveData like this:

val items : LiveData<ArrayList<String>> = savedStateHandle.getLiveData(KEY, arrayListOf())

This code makes sure the list of items comes from the persisted data if there is any. However, you haven’t yet saved any data, you’ll do that next.

Saving The ViewModel State With SavedStateHandle

A good time to save items would be when they’re added to the list.

Add the following function to ShoppingListViewModel.kt

fun addItemToShoppingList(itemName: String) {
  items.value!!.add(itemName)
  savedStateHandle.set(KEY, items.value)
}

This code saves the entire list of items using the savedStateHandle each time an item is added. In this case, the list of items is an array of Strings, which is supported by SavedStateHandle. You can persist more complex objects as long as they implement the Parcelable interface.

Now that ShoppingListViewModel requires a SavedStateHandle, you’ll have to change the way it’s currently instantiated. You’ll tackle that next.

Instantiating ViewModel With Activity

Next, open ShoppingListActivity.kt and replace this line in the onCreate() method:

viewModel = ViewModelProviders.of(this).get(ShoppingListViewModel::class.java)

with:

viewModel = ViewModelProviders.of(this, SavedStateViewModelFactory(this)).get(ShoppingListViewModel::class.java)

Here you added a SavedStateViewModelFactory that handles the system initiated death and preserves the state of the data that the view model holds at that point of time.

Next, start fetching items using the new getter you implemented.

In onCreate() change the way you refer to the ViewModel‘s items like this:

  val position = viewModel.items.value?.size

There’s one more step before it starts working as expected.

Gluing it All Together

Replace your loadShoppingLists() method with this code:

// 1
private fun loadShoppingLists() {
    // 2
    viewModel.loadShoppingList()
    // 3
    viewModel.getItems().observe(this, Observer<ArrayList<String>> {
        shoppingListAdapter = ShoppingListAdapter(it ?: arrayListOf())
        recyclerView.adapter = shoppingListAdapter
        shoppingListAdapter.notifyDataSetChanged()
    })
}
  1. This function makes sure you get the updated shopping list and is called from onResume of the ShoppingListActivity.
  2. You call loadShoppingList which helps you set the data in your list with the updated list, if your list is empty.
  3. Here, you observe the changes in the list and notify the recyclerView.

Now it’s time to test the changes you made to the code. Build and run the app. Try adding some items to the list.

Next, press the Home button to send the app to the background. Then run this command in your terminal:

$ adb shell am kill com.raywenderlich.hopelesshubby

Next, try launching the app on the device again. You’ll see the items you added to the list persist automatically.

Voila, you did it! You made the lives of some hopeless hubbies a little better. Pat yourself on the back. :]

Diving Into Different Types of Data for Persistence

SavedStateHandle class offers the methods you expect for a key-value map:

  • get(String key).
  • contains(String key).
  • remove(String key).
  • set(String key, T value).
  • keys().

It accepts all the primitive types and their respective arrays in addition to Parcelable, Serializable, SparseArray, Bundle, ArrayList and Binder. For an exhaustive list, check this page.