Nerd For Tech
Published in

Nerd For Tech

Why Android ViewModels are obsolete in KMM

The Problem

One of the challenges on Android is that UI state needs to be saved and restored when configuration changes like screen rotations happen. Android ViewModels were introduced to make this process as painless as possible.

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.(https://developer.android.com/topic/libraries/architecture/viewmodel)

While designing and implementing a UI framework for Kotlin Multiplatform I found that existing frameworks use one of two approaches to address the (Android specific) UI state retention problem:

  1. Use a ViewModel on Android that proxies from the UI to the component that implements the business logic (let’s call it bloc = business logic component). The sole reason for the ViewModel to exist is to be lifecycle aware and retain state across configuration changes.
    Touchlab has made a virtue out of this in their KaMPKit and created a shared ViewModel with platform specific implementations so all business logic can be put into the shared ViewModel.
  2. Add the ability to preserve state / retain instances in the bloc = business logic component. Decompose e.g. is using Essenty’s StateKeeper and InstanceKeeper. This makes the bloc independent of the Android ViewModel (at least on the surface) but now it needs to explicitly save and restore state. This is imo a less than ideal solution especially because it’s a solution to a problem that exists on a single platform only.

A “better” Solution

There’s a third way.

Just to recap, we need a bloc / business logic component that is lifecycle aware and is retained across configuration changes. This is how my solution looks like in an Activity or a Fragment:

val bloc by getOrCreate { bloc(it) } // `it` is an Essenty lifecycle

We’re using an Essenty lifecycle which is the platform-independent version of Android lifecycle. It’s passed as argument to the bloc and can be used by the business logic to start and stop and cleanup resources upon destruction (onDestroy() called).

Let’s see what’s under the hood:

getOrCreate is an extension function of ViewModelStoreOwner lazily creating a component using the builder function (lifecycle: Lifecycle) -> Component. Activities and Fragments are both ViewModelStoreOwner so we can call this from either.

ComponentLazy is a bit harder to understand:

(Note: with androidx.lifecycle 2.5, the create function’s signature has slightly changed to:
override fun <T : ViewModel> create(modelClass: Class<T>): T
-> T isn’t nullable any more)

Using the ViewModelStoreOwner we create a ViewModelProvider which will create ViewModels and retain them in a store of the given ViewModelStoreOwner. The get function returns an existing ViewModel or creates a new one in the scope (fragment or activity), associated with this ViewModelProvider. The created ViewModel is associated with the given scope and will be retained as long as the scope is alive (e.g. if it is an activity, until it is finished or process is killed):

  • The created ViewModel is a BlocViewModel which holds an Essenty Lifecycle and an Essenty InstanceKeeper:
  • Now we use this InstanceKeeperto either retrieve or create the Component. The Component is wrapped into a class than implements InstanceKeeper.Instance because only those can be store in an InstanceKeeper:

In a gist:

  • The Component is stored in an InstanceKeeper
  • The InstanceKeeper is stored in a BlocViewModel which is an Android ViewModel
  • The BlocViewModel holds a Lifecycle tied to its own lifecycle. This lifecycle is used by the Component to manage its internal resources (coroutines, flows, channels etc.)
  • The BlocViewModel is stored in the ViewModelStore as long as its owner is alive meaning it will be retained across configuration changes

Summary

We can create a shared / platform-independent business logic component and make it lifecycle aware on Android with a single line of code:

val bloc by getOrCreate { bloc(it) }

The code that makes this possible can reside in a shared module (androidMain). Kotlin Bloc, a recently released UI framework for Kotlin Multiplatform, implements this mechanism.

Thanks for reading and for your feedback.

Addendum

Note that while using a ViewModel under-the-hood makes sure to retain the bloc across configuration changes, it won’t survive a process death.

In order to survive a system-initiated process death, you’d still have to use a SavedStateHandle:

https://developer.android.com/topic/libraries/architecture/saving-states

Whether your UI needs that kind of “retention” depends on your app. Depending upon the action a user takes, they either expect that activity state to be cleared or the state to be preserved.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store