44. 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.

44.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 44-1 (do not click on the stop button in the Android Studio toolbar):

Figure 44-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.

44.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

 

class MainViewModel : ViewModel() {

.

.

}

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

private lateinit var viewModel: MainViewModel

 

viewModel = ViewModelProvider(this).get(MainViewModel::class.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 androidx.lifecycle.ViewModel

import androidx.lifecycle.MutableLiveData

import androidx.lifecycle.SavedStateHandle

 

class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

.

.

}

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

private lateinit var viewModel: MainViewModel

 

val factory = SavedStateViewModelFactory(activity.application, this)

viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)

44.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:

val 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:

var restoredName: LiveData<String> = savedStateHandle.getLiveData(NAME_KEY)

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

var restoredName: String? = 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.

44.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.kt file so that the constructor accepts 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:

package com.ebookfrenzy.viewmodeldemo.ui.main

 

import androidx.lifecycle.ViewModel

import androidx.lifecycle.MutableLiveData

import androidx.lifecycle.SavedStateHandle

 

const val RESULT_KEY = "Euro Value"

 

class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    

    private val rate = 0.74f

    private var dollarText = ""

private var result: MutableLiveData<Float> =  

                savedStateHandle.getLiveData(RESULT_KEY)

.

.

}

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

fun setAmount(value: String) {

    this.dollarText = value

    val convertedValue = value.toFloat() * rate

    result.value = convertedValue

    savedStateHandle.set(RESULT_KEY, convertedValue)

}

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

.

.

import androidx.lifecycle.SavedStateViewModelFactory

.

.

   override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

 

        activity?.application?.let {

            val factory = SavedStateViewModelFactory(it, this)

            viewModel = ViewModelProvider(this, factory)

                         .get(MainViewModel::class.java)

 

            val resultObserver = Observer<Float> {

                     result -> resultText.text = result.toString() }

.

.

            }

        }

.

.

}

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.

44.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.238.71.155