Lifecycle aware view models: A much-needed feature for separation of concerns.

Lifecycle aware view models: A much-needed feature for separation of concerns.

ยท

6 min read

This article is in continuation of the first part where we discuss how to create lifecycle-aware components in Android. If you missed that article I highly recommend you to read it here as it explains in detail how lifecycle awareness works.

In this article, we will take a look at how we can use our knowledge of creating lifecycle-aware components to create a lifecycle-aware ViewModel. This would then allow us to access the lifecycle methods of the activity inside the ViewModel without overriding them in the activity itself.

At the end of this article, I will demonstrate a customized ViewModel class that is already lifecycle aware and has lifecycle methods that can be overridden.

In this article, we touch upon the following points:

  • How to make any ViewModel class lifecycle aware
  • How to make a custom ViewModel class that has lifecycle awareness of the associated activity.

So let's get started with it.

Lifecycle-aware viewmodel

In the previous article we looked at how we can make any class lifecycle aware and let's try to apply the same principles to make a ViewModel class lifecycle aware.

To do so we need to make the viewmodel a lifecycleObserver and hook it into the lifecycle of the activity. The below example showcases this

class MainViewModel: ViewModel(), LifecycleObserver {
    //ViewModel class code ....
} 


class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(viewModel)
     }
}

The LifecycleObserver is a marker interface that tells the system that this class can hook into the lifecycle object to observe changes. Since it is a marker interface in order to access the lifecycle methods we have to dependent on something else which will be shown later.

Since the androidx.AppCompatActivity class extends the ComponentActivity, it has an associated lifecycle object that can be hooked into. In the onCreate method we add a new observer to the lifecycle object which is our ViewModel class.

With this done we have now made our ViewModel lifecycle aware and we can start using the lifecycle methods inside the ViewModel.

In order to access the lifecycle methods inside the ViewModel class we have to use the @OnLifecycleEvent annotation and use it with an Event value.


class MainViewModel : ViewModel(), LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() {

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() {

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {

    }

}

Since the LifecycleObserver is a marker interface we have to depend upon the @OnLifecycleEvent annotation to access the lifecycle methods of the activity.

You can create any function with any name and attach the @OnLifecycleEvent annotation with an Event that will cause the method to be invoked only when that lifecycle event occurs in the activity. It is also not necessary to implement each and every lifecycle method inside the ViewModel and you can invoke only those methods that you are interested in.

With this much implementation, we can make any ViewModel lifecycle aware. Your activity classes will look much cleaner now that you have all lifecycle-related business logic moved to the ViewModel class.

But even though this much implementation is enough for accessing the ViewModel classes, I was still facing some issues where I would forget to add lifecycle.addObserver() method inside the activity class which made me wonder if there could be a way through which ViewModel initialization automatically adds it as an observer.

Custom lifecycle-aware viewmodel

In order to automatically add the ViewModel as an observer in the activity we will have to look at the initialization code of the ViewModel.

To be specific we need to understand this code.

 private val viewModel: MainViewModel by viewModels()

The viewModels() is a lazy delegate for ViewModel initialization. If we look at the internal code for this lazy init delegate we will find this


/**
 * Returns a [Lazy] delegate to access the ComponentActivity's ViewModel, if [factoryProducer]
 * is specified then [ViewModelProvider.Factory] returned by it will be used
 * to create [ViewModel] first time.
 *
 * 
 * class MyComponentActivity : ComponentActivity() {
 *     val viewmodel: MyViewModel by viewmodels()
 * }
 * 
 *
 * This property can be accessed only after the Activity is attached to the Application,
 * and access prior to that will result in IllegalArgumentException.
 */
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {

    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

You don't need to understand every part of this code but only a few things.

  1. The viewModels() is an extension function on top of a ComponentActivity.
  2. The viewModels() return a Lazy or Lazy ViewModel instance.

Since it is an extension function on top of Component activity we can access the lifecycle object inside the function itself. Lazy is just another interface that has a variable called value which returns the actual value of the ViewModel class.

public interface Lazy<out T> {
    /**
     * Gets the lazily initialized value of the current Lazy instance.
     * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
     */
    public val value: T

    /**
     * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
     * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
     */
    public fun isInitialized(): Boolean
}

Now let's customize this to make our own implementation of viewModels() function that adds the ViewModel as a lifecycle observer.

@MainThread
inline fun <reified VM> ComponentActivity.lifecycleViewModels(
    noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> where VM : ViewModel, VM: LifecycleObserver {

    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    val vm =  ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
    lifecycle.addObserver(vm.value)

    return vm
}

Notice how the VM generic now implements both ViewModel and LifecycleObserver. This ensures that only ViewModels that implement the LifecycleObserver interface can be initialized using this function.

I have also renamed the function to lifecycleViewModels to differentiate between the normal viewModels extension function and our custom extension function.

Now we can use this in the activity as

class MainActivity : AppCompatActivity() {

private val viewModel: MainViewModel by lifecycleViewModels()

}

and with this, you can directly invoke a lifecycle-aware ViewModel class.

Conclusions


A lifecycle-aware ViewModel class can help you move the lifecycle-related methods to the ViewModel class. This makes the activity class bloat free and only responsible for ui-related logic. The customized lifecycleViewModels() extension function allows you to remove the boilerplate code of adding ViewModel to the lifecycle of the activity.

Also, this doesn't have to stop here, you can create a lifecycleViewModels() extension function on top of the fragment as well to invoke lifecycle-aware ViewModel inside the fragment.

If you think this article has helped you understand lifecycles better, consider sharing this article with your friends.

ย