Optimizing Android ViewModel with Lifecycle 2.2.0

Initialization, passing arguments, and saved state

The Lifecycle 2.2.0 update including simplified initialization with by viewModels() , by activityViewModels() , and by navGraphViewModels(...) syntax for the ViewModel (VM) component is great for quickly creating VMs. What about re-using the VM instance throughout fragments, passing arguments/parameters into the VM while also enabling saved state? With a bit of customization, the above can be achieved as I recently shipped in the latest version of the Coinverse app.

Sample Code

Setup

Initializing the ViewModel for Reusability

How to pass Arguments/Parameters to ViewModels

Enabling SavedState with Arguments/Parameters

Resources

Setup

To take advantage of the latest VM component, declare the most recent Lifecycle dependencies in the build.gradle (Module: app) file for lifecycle-viewmodel-ktx and lifecycle-livedata-ktx . The jvmTarget also needs to be defined in order to implement the by viewModels syntax we’ll use below.

If the navigation component version of the VM is being implemented, the navigation components’ libraries, navigation-ui-ktx and navigation-fragment-ktx are required.

Initializing the ViewModel for Reusability

Photo by Stephanie Moody on Unsplash

As the ViewModel documentation outlines, a major advantage of utilizing VMs in an activity/fragment is the ability to save view state data that comprises the user interface. Otherwise, large portions of data would need to be reloaded every time there is a configuration change, or when the app temporarily goes into the background.

The documentation shows initiating the VM in onCreate as a method level var , and then later on as a class level val . Creating the VM in onCreate requires reassigning the variable on the class level in order to use the VM throughout the activity/fragment. For this reason, it’s preferred to create the VM as a class level val .

Rather than creating a lateinit var instance variable, declare a VM immutable val . The VMs values can only be accessed after the activity is attached to the application.

🔎 Differences between by viewModels, activityViewModels, and navGraphViewModels

Use by viewModels when the activity/fragment creating the VM will be the only activity/fragment accessing the VM’s data.

Behind the scenes viewModels is an extension function applied to either an activity/fragment that returns a VM with a lifecycle tied to that activity/fragment. A VM factory may optionally be defined which can be used to pass parameters/arguments as we’ll see below.

Use by activityViewModels in fragments, when the fragment is sharing data/communicating with another activity/fragment.

This is useful for sharing information between views. activityViewModels is an extension function applied to a fragment that returns a fragment’s parent’s activity’s VM. A VM factory may be defined here too.

Use by navGraphViewModels(R.id.some_nav_graph) in a fragment where the VM benefits by being scoped to a specific navigation graph or nested graph.

This is useful for managing the business logic of features organized in a nested graph to ensure sure data is not leaked to other parts of the app, and that the lifecycle is handled properly. A VM factory may be defined here as well.

See: ViewModel and Jetpack Navigation : NavGraph with a ViewModel

📦 Storing view state data

The VM is a great location to store cached view state data using an observable data structure such as LiveData, Coroutines, or RxKotlin.

Use immutable public values to observe data.

Use mutable private variables to edit values in the VM.

Use a pattern that allows the readability of the values in the VM, such as LiveData’s transform or Coroutine’s collect.

♻️ VM lifecycle overview

Created the first time an activity calls onCreate

When an activity is recreated, it receives the same VM instance.

When the creating activity/fragment is finished/detached, VM’s onCleared is called

How to pass Arguments/Parameters to ViewModels

Photo by Wilhelm Gunkel on Unsplash

The advantage of passing data into the VM upon creation is that an important process may begin upon VM initialization, such as retrieving data to populate a view, rather than the process being tied to the fragment/activity lifecycle. This is important because as Lyla Fuijwara, a Google developer advocate explains, the VM instance is created once inside the activity/fragment. Then, the VM is stopped only when a user closes the view holding the VM, closes the app entirely, or if the Android system needs to clear the memory while the app is in the background.

If an initialization process is tied to an activity/fragment lifecycle event, it may unintentionally occur if the activity/fragment is recreated. When the process runs in the VM’s init{..} function, it will be tied to the VM lifecycle.

See: ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders

🤷🏻‍♂ Can’t we just use AndroidViewModel (AVM)?

AVM is an Android architecture component after all and allows one to pass parameters while providing application context. This seems tempting at first, however, it is problematic for unit tests. Unit tests should not deal with any of the Android lifecycle, such as context.

☝🏻 Data not to pass to the ViewModel

Context, views, activities, fragments, adapters, Lifecycle, observe lifecycle-aware observables or hold resources, drawables etc. This information should be handled by the view and tethered to the view lifecycle.

Don’t load resources, instead pass resource ids, and load in view. See: Locale changes and the AndroidViewModel antipattern

Enabling SavedState with Arguments/Parameters

Photo by Maurizio Pesce on flickr

The saved state module is a lifecycle component enabling saving data in the VM that will persist when the VM is stopped by the Android system. This may replace the functionality of saving data inside onSaveInstanceState (OSIS) using the activity/fragment bundle. Lyla also highlights the differences between onSaveInstanceState and SavedState (SS) in detail. The main difference with SS is that the data is stored within the VM instead of in the activity/fragment with OSIS.

OSIS and SS are meant to serve the same use case, saving small amounts of information required to restore the view state of the screen. The majority of data in the VM is meant for saving larger amounts of data that make up the view state. Therefore, SS may replace OSIS in order to organize both the smaller and larger view state data in one place, the VM.

See: How do I use ViewModels to save and restore UI state efficiently?

With the Lifecycle 2.2.0 release, the saved state module comes standard with by viewModels or by activityViewModels syntax, without needing to create a ViewModelProvider.NewInstanceFactory . However, if setting default values for the saved state module or passing arguments/parameters to a VM, an AbstractSavedStateViewModelFactory factory is required, as the documentation notes. The factory class must handle the saved state owner, default values, and custom parameters.

🚦DefaultArgs

The Bundle defaultArgs is recommended to pass into the ViewModelFactory, as the values will be used as defaults in the SavedStateHandle if there is not a value for the given key being requested by the saved state.

See: Public constructors of the AbstractSavedStateViewModelFactory

💾 Saved State

An Int is saved with the method saveSomeInt , in the SavedStateHandle to represent important information that needs to survive the VM being cleared. Then, someInt may be retrieved when needed. Because a default argument is defined for the key SOME_INT_KEY , the saved state will always have a value to work with for that key.

Resources