ViewModel with SavedState

In Google I/O 2018 Google Launched Android Jetpack which is a set of components, tools and guidance to make great Android apps. It consisted of LiveData, ViewModel, Room Database, Work Manager etc.In this blog we will be talking about ViewModel.

ViewModel manages UI related data in Activity lifecycle. It allows to survive configuration changes in the App(like Screen rotation).

ViewModel are basically used for,

  1. Prepare data for the UI layer.
  2. Handles the configuration of App rotation.
PS: the ViewModel class should not contain the reference of the View (Activity/Fragment).
Above Figure shows the ViewModel Lifecycle.

Implementation of a ViewModel,

class MainViewModel : ViewModel() {
    val blogs = MutableLiveData<List<Blog>>()
    fun getBlogs(): LiveData<List<Blog>> {
        return blogs
    }
    private fun loadBlogs() {
        blogs.value = //our list of blogs
    }
}

and to get the data in on View,

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MainViewModel instance created by the first activity.
        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)
        model.getBlogs().observe(this, Observer<List<Blog>>{ blogs ->
            // update UI
        })
    }
}

In the View (Activitys/Fragments), we have to initalise the ViewModel using,

       val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

and we can observe the data using in UI,

model.getBlogs().observe(this, Observer<List<Blog>>{ blogs ->
            // update UI
        })

This is how we use ViewModel in our Views.You can learn more about ViewModel here.

Now, in ViewModel we can only handle the configuration changes in the View but it does not store the State of UI or the View. But it does not handle the System Initiated Process Death. To handle it , we can use onSaveInstanceState() to restore the state we left in.

System Initiated Process Death can be defined as , every Android application runs in its own Linux process. This process is created for the application when some of its code needs to be run, and will remain running until it is no longer needed and the system needs to reclaim its memory for use by other applications.

If your app is not in the foreground, the system can terminate your process at any point to free up system RAM for use by other processes.

  • onSaveInstanceState() bundle can handle both configuration and System Initiated process death. When we re-open the app and move our app to foreground we can still see our data which was persisted using onSaveInstanceState().
  • But it can only store a limited amount of data and depends a lot on speed and storage as Serialization can take a lot of memory to store the data.
  • Serialization occurs on main thread so while configuration changes it can block the UI screen and might also let the app freeze of produces(ANRs) i.e. App Not Responding.
  • onSaveInstanceState() can store a minimum number of data and not large amount of it.
In Google I/O 2019 , Google released something called Saved State For ViewModel

Saved State of ViewModel can be considered as a replacement for onSaveInstanceState() as it can also store the data because UI data is always referenced from ViewModel of Architecture Components and not the View (Activity/Fragment). So to use onSaveInstanceState() will require us to do some coding.

Who wants to write some extra code?? No on right?

So, as part of Jetpack Google launced something called Saved State which helps us to store and retrieve the data from saved state after it goes through the System Initiated Process Death.

Note: State must be simple and lightweight. For complex or large data you should use local persistence.

To Integrate the Saved State in Project,

add the below code in your build.gradle of your app component

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

How to use Saved State?

update the below code snippet in the onCreate() of your View (Activity/Fragment),

val model = ViewModelProviders.of(this, SavedStateVMFactory(this)).get(MainViewModel::class.java)

in place of,

val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

And in ViewModel,

class MainViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Here, you can see we have passed SavedStateHandle in the primary constructor of the ViewModel. In order to get SavedStateHandle we pass SavedStateVMFactory() in our ViewModelProviders.of as Factory.

A Factory is an Interface which tells the ViewModel , how to create the ViewModel.

Here, SavedStateHandle is a handle which is in Key-Value map that will let you read and write objects to and from the saved state. The data will still be persisted even if the app goes through System Initiated Process Death.

  • SavedStateHandle is just like SharedPreferences in Android which work on Key-Value pair.

Let's Understand it better with an Example,

  • We will be building an App with 3 UI components(EditText, Button and TextView).
  • When User Enters the Username in the EditText and Clicks the Button, it should display the username in the TextView.

This is what we are going to build,

Now the MainViewModel will look like this,

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

    override fun getUserName(): LiveData<String> {
        return savedStateHandle.getLiveData(Constants.USERNAME)
    }
    override fun saveUserName(username: String) {
        savedStateHandle.set(Constant.USERNAME, username)
    }
}

Here,

  • savedStateHandle.set("Key","Value") is used to store the data
  • savedStateHandle.getLiveData("key") is used to return the LiveData of String datatype

The BaseViewModel looks like the following and it is implemented by MainViewModel Class.

interface BaseViewModel {
    fun getUserName(): LiveData<String>
    fun saveUserName(username: String)
}

And finally the MainActivity class file looks like,

class MainActivity : AppCompatActivity() {
    lateinit var mainViewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val factory = SavedStateVMFactory(this@MainActivity)
        mainViewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
        setupActions()
    }

    private fun setupActions() {
        submitButton.setOnClickListener {
            mainViewModel.saveUserName(username_edittext.text.toString()
        }
        mainViewModel.getLiveDataUserName().observe(this, Observer {
            saved_textview.text= "LiveData Result: $it"
        })
    }
}

Now, to test the SavedState you need a Emulator/Device atleast targetting Android Pie(+). We have to follow the following state to simulate the System Initiate Process Death,

  • We have to make sure that , the App is currently running in the Android Device by
adb shell ps -A | grep lifecycle
  • It will show an Output with the name of Your App's lifecycle like "com.mindorks.app" in your terminal
adb shell am kill your-app-package-name
  • And to confirm, you should run the Step - 1 and you won't see your app's package name in the terminal.
  • Now re-open the app and you would see the output being persited and displayed in the TextView of the Screen.

Points to Note :-

  1. If you want to set the data in savedState use,
savedStateHandle.set("key_name","value")

2. If you want to get the data from savedState use,

savedStateHandle.get("key_name")

3. If you want to get LiveData as return type use,

savedStateHandle.getLiveData("key_name")

4. If you want to check that specific key is present in savedState is,

savedState.contains("key_name")

and it returns boolean data type when true means savedState contains the value and false means it doesn't.

5. If you want to find all the keys in the savedState use the following , which will return the list of keys.

savedState.keys()

6. And to delete any specific value, you can access it via its key to delete it. To do the following use,

savedState.remove("key_name")
This ends this article. I hope you must have got the concepts of using Viewmodel with SavedState in an Android application.