37. An Android ViewModel Saved State Tutorial

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

As outlined in the chapter entitled “Understanding Android Application and Activity Lifecycles”, when the user places an app into the background that app becomes eligible for termination by the operating system in the event that resources become constrained. When the user attempts to return the terminated app to the foreground, Android simply relaunches the app in a new process. Since this is all invisible to the user, it is the responsibility of the app to restore itself to the same state it was in when the app 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.

37.1 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 have not yet created the project, load into Android Studio the ViewModelDemo_LiveData project from the sample code download that accompanies 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, once the app has responded to the orientation change, both values are still visible.

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 into the background, then terminate it by opening the Logcat tool window in Android Studio and clicking on the terminate button as highlighted in Figure 37-1 (do not click on the stop button in the Android Studio toolbar):

Figure 37-1

Once the app has been terminated, return to the device or emulator and select the app from the launcher (do not simply re-run the app from within Android Studio). Once the app appears, it will do so as if it was just launched, with the previous dollar and euro values lost. From the perspective of the user, however, the app was simply 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.

37.2 Implementing ViewModel State Saving

Basic ViewModel state saving is made possible through the introduction of the ViewModel Saved State library. This library essentially 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 saved state, a different kind of ViewModel subclass needs to be declared, in this case one 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:

package com.ebookfrenzy.viewmodeldemo.ui.main;

 

import androidx.lifecycle.ViewModel;

import androidx.lifecycle.MutableLiveData;

 

public class MainViewModel extends ViewModel {

.

.

}

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

private MainViewModel mViewModel;

 

mViewModel = new ViewModelProvider(this).get(MainViewModel.class);

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;

    }

.

.

}

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

SavedStateViewModelFactory factory =

       new SavedStateViewModelFactory(requireActivity().getApplication(),this);

 

mViewModel = new ViewModelProvider(this).get(MainViewModel.class);

37.3 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);

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:

MutableLiveData<String> restoredName = savedStateHandle.getLiveData(NAME_KEY);

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

String restoredName = savedStateHandle.get(NAME_KEY);

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 the keys contained within the saved state.

37.4 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 Jetpack LiveData Tutorial” into Android Studio (a copy of the project is also available in the sample code download), opening the build.gradle (Module: ViewModelDemo.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.1.0"

    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1"

.

.

}

Next, modify the MainViewModel.java file so that 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 = "";

    private SavedStateHandle savedStateHandle;

    private MutableLiveData<Float> result;

 

    public MainViewModel(SavedStateHandle savedStateHandle) {

        this.savedStateHandle = savedStateHandle;

        result = savedStateHandle.getLiveData(RESULT_KEY);

    }

.

.

}

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:

public void setAmount(String value) {

    this.dollarText = value;

    result.setValue(Float.parseFloat(dollarText)*rate);

    Float convertedValue = Float.parseFloat(dollarText)* rate;

    result.setValue(convertedValue);

    savedStateHandle.set(RESULT_KEY, convertedValue);

}

With the changes to the ViewModel complete, open the MainFragment.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 onActivityCreated(@Nullable Bundle savedInstanceState) {

        super.onActivityCreated(savedInstanceState);

 

        SavedStateViewModelFactory factory =

          new SavedStateViewModelFactory(

                        requireActivity().getApplication(),this);

 

        mViewModel = new ViewModelProvider(this).get(MainViewModel.class);

.

.

}

After completing the changes, build and run the app and perform a currency conversion. With the screen UI populated with both the dollar and euro values, place the app into the background, terminate it from the Logcat tool window 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.

37.5 Summary

A well designed app should always present the user with the same state when brought forward from the background, regardless of whether the process containing the app was terminated by the operating system 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.60.166