Saving ViewModel Saved State in Java Tutorial

The preservation and restoration of app state is about presenting the user with continuity in appearance and behavior after an app is placed in the background. Users expect to be able to switch from one app to another and, on returning to the original app, find it in the exact state it was in before the switch took place.

As outlined in the chapter entitled Android App and Activity Lifecycles, when the user places an app in the background, that app becomes eligible for termination by the operating system if resources become constrained. When the user attempts to return the terminated app to the foreground, Android relaunches the app in a new process. Since this is all invisible to the user, it is the app’s responsibility to restore itself to the same state it was in when it was originally placed in the background instead of presenting itself in its “initial launch” state. In the case of ViewModel-based apps, much of this behavior can be achieved using the ViewModel Saved State module.

Understanding ViewModel State Saving

As outlined in the previous chapters, the ViewModel brings many benefits to app development, including UI state restoration in the event of configuration changes such as a device rotation. To see this in action, run the ViewModelDemo app (or, if you still need to create the project, load into Android Studio the ViewModelDemo_ LiveData project from the sample code download accompanying the book).

Once running, enter a dollar value and convert it to euros. With both the dollar and euro values displayed, rotate the device or emulator and note that both values are still visible once the app has responded to the orientation change.

Unfortunately, this behavior does not extend to the termination of a background app process. With the app still running, tap the device home button to place the ViewModelDemo app in the background, then terminate it by opening the Terminal tool window and running the following command (where <package name> is the name you used when the project was created, for example, com.ebookfrenzy.viewmodeldemo):

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Iguana – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 840 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

adb shell am kill <package name>Code language: plaintext (plaintext)

If the adb command is not found, refer to the chapter “Setting up an Android Studio Development Environment” for steps to set up your Android Studio environment.

Once the app has been terminated, return to the device or emulator and select the app from the launcher (do not re-run the app from within Android Studio). Once the app appears, it will do so as if it was just launched, with the last dollar and euro values lost. From the user’s perspective, however, the app was restored from the background and should still have contained the original data. In this case, the app has failed to provide the continuity that users have come to expect from Android apps.

Implementing ViewModel State Saving

Basic ViewModel state saving is made possible through the introduction of the ViewModel Saved State library. This library extends the ViewModel class to include support for maintaining state through the termination and subsequent relaunch of a background process.

The key to saving state is the SavedStateHandle class which is used to save and restore the state of a view model instance. A SavedStateHandle object contains a key-value map that allows data values to be saved and restored by referencing corresponding keys.

To support state saving, a different kind of ViewModel subclass needs to be declared, in this case containing a constructor which can receive a SavedStateHandle instance. Once declared, ViewModel instances of this type can be created by including a SavedStateViewModelFactory object at creation time. Consider the following code excerpt from a standard ViewModel declaration:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Iguana – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 840 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

package com.ebookfrenzy.viewmodeldemo.ui.main;
 
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.MutableLiveData;
 
public class MainViewModel extends ViewModel {
.
.
}Code language: Java (java)

The code to create an instance of this class would likely resemble the following:

private MainViewModel mViewModel;
 
mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
Code language: Java (java)

A ViewModel subclass designed to support saved state, on the other hand, would need to be declared as follows:

package com.ebookfrenzy.viewmodeldemo.ui.main;
 
import android.util.Log;
 
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.SavedStateHandle;
 
public class MainViewModel extends ViewModel {
 
    private SavedStateHandle savedStateHandle;
 
    public MainViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
    }
.
.
}Code language: Java (java)

When instances of the above ViewModel are created, the ViewModelProvider class initializer must be passed a SavedStateViewModelFactory instance as follows:

SavedStateViewModelFactory factory =
       new SavedStateViewModelFactory(getActivity().getApplication(),this);
 
mViewModel = new ViewModelProvider(this).get(MainViewModel.class);Code language: Java (java)

Saving and Restoring State

An object or value can be saved from within the ViewModel by passing it through to the set() method of the SavedStateHandle instance, providing the key string by which it is to be referenced when performing a retrieval:

private static final String NAME_KEY = "Customer Name";
 
savedStateHandle.set(NAME_KEY, customerName);
Code language: Java (java)

When used with LiveData objects, a previously saved value may be restored using the getLiveData() method of the SavedStateHandle instance, once again referencing the corresponding key as follows:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Iguana – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 840 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

MutableLiveData<String> restoredName = savedStateHandle.getLiveData(NAME_KEY);Code language: Java (java)

To restore a normal (non-LiveData) object, use the SavedStateHandle get() method:

String restoredName = savedStateHandle.get(NAME_KEY);Code language: Java (java)

Other useful SavedStateHandle methods include the following:

  • contains(String key) – Returns a boolean value indicating whether the saved state contains a value for the specified key.
  • remove(String key) – Removes the value and key from the saved state. Returns the value that was removed. • keys() – Returns a String set of all keys contained within the saved state.

Adding Saved State Support to the ViewModelDemo Project

With the basics of ViewModel Saved State covered, the ViewModelDemo app can be extended to include this support. Begin by loading the ViewModelDemo_LiveData project created in An Android Studio Java LiveData Tutorial into Android Studio (a copy of the project is also available in the sample code download), opening the build.gradle.kts (Module :app) file and adding the Saved State library dependencies (checking, as always, if more recent library versions are available):

.
.
dependencies {
.
.    
    implementation ("androidx.savedstate:savedstate:1.2.1")
    implementation ("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
.
.
}Code language: Gradle (gradle)

Next, modify the MainViewModel.java file so the constructor accepts and stores a SavedStateHandle instance. Also, import androidx.lifecycle.SavedStateHandle, declare a key string constant and modify the result LiveData variable so that the value is now obtained from the saved state in the constructor:

package com.ebookfrenzy.viewmodeldemo.ui.main;
 
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.SavedStateHandle;
 
public class MainViewModel extends ViewModel {
 
    private static final String RESULT_KEY = "Euro Value";
    private static final Float rate = 0.74F;
    private String dollarText = "";
    final private SavedStateHandle savedStateHandle;
    final private MutableLiveData<Float> result = new MutableLiveData<>();
 
    public MainViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        result = savedStateHandle.getLiveData(RESULT_KEY);
    }
.
.
}Code language: Java (java)

Remaining within the MainViewModel.java file, modify the setAmount() method to include code to save the result value each time a new euro amount is calculated:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Iguana – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 840 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

public void setAmount(String value) {
    this.dollarText = value;
    result.setValue(Float.valueOf(dollarText)* rate);
    Float convertedValue = Float.parseFloat(dollarText)* rate;
    result.setValue(convertedValue);
    savedStateHandle.set(RESULT_KEY, convertedValue);
}Code language: Java (java)

With the changes to the ViewModel complete, open the FirstFragment.java file and make the following alterations to include a Saved State factory instance during the ViewModel creation process:

.
.
import androidx.lifecycle.SavedStateViewModelFactory;
.
.
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    SavedStateViewModelFactory factory =
            new SavedStateViewModelFactory(
                    getActivity().getApplication(),this);
 
    mViewModel = new ViewModelProvider(this, factory).get(MainViewModel.class);
    // TODO: Use the ViewModel
}Code language: Java (java)

With the screen UI populated with dollar and euro values, place the app into the background, terminate it using the adb tool, and then relaunch it from the device or emulator screen. After restarting, the previous currency amounts should still be visible in the TextView and EditText components, confirming that the state was successfully saved and restored.

Summary

A well-designed app should always present the user with the same state when brought forward from the background, regardless of whether the operating system terminated the process containing the app in the interim. When working with ViewModels, this can be achieved by taking advantage of the ViewModel Saved State module. This involves modifying the ViewModel constructor to accept a SavedStateHandle instance which, in turn, can be used to save and restore data values via a range of method calls. When the ViewModel instance is created, it must be passed a SavedStateViewModelFactory instance. Once these steps have been implemented, the app will automatically save and restore state during a background termination.


Categories ,