How to make ViewModel observe Android Lifecycle Events, and how to Unit Test Lifecycle Events

jeffreysoboe
3 min readMar 16, 2021

--

Sometimes we want to perform certain logic when an Android Lifecycle Event occurs. Let’s say your Activity switches from onCreate() to onStart(), and every time the screen appears you want to reload data or do some other work.

Using an MVVM architecture, that might look something like this in your Activity class:

override fun onStart() {
super.onStart()
myViewModel.onStart() // load data in this method
}
override fun onResume() {
super.onResume()
myViewModel.onResume() // or load data in this method
}

However, it might be nice if we could instead have the ViewModel itself observe the lifecycle changes. This way we can observe any lifecycle event we want within the ViewModel itself, and ultimately will be able to write more testable code, with less exposed functions. After all, we don’t want the Activity to be able to call onStart() within onCreate()! To do that, let’s implement LifecycleObserver in our ViewModel.

MyViewModel: ViewModel(repository: MyRepository), LifecycleObserver

Great! Do we need to override anything? Nope! Instead, we need to make sure that our ViewModel now observes the lifecycle of the view it is attached to. So inside our Activity, we can now add MyViewModel as an observer. Any changes to the Lifecycle.State or Lifecycle.Event will now be transmitted to MyViewModel.

override fun onCreate(savedInstanceState: Bundle?) {
// ...
lifecycle.addObserver(myViewModel)
}

Next, we need to make sure we respond to the events. In your ViewModel, add a function with the @OnLifecycleEvent annotation, along with the event type you are listening for.

@OnLifecycleEvent(Lifecycle.Event.ON_START)
private fun onStart() {
repository.reloadData()
}

Great! Your ViewModel will now call repository.reloadData whenever the lifecycle goes through onStart.

But how do we test the LifecycleEvents?

The simple way to do this is to simply call viewModel.onStart() in your JUnit tests, but this is not great since it forces us to expose a public onStart() method, and it doesn’t ensure we have the correct annotation in the ViewModel to actually observe the real lifecycle.

Instead, let’s have our ViewModel observe some mock event changes in our JUnit tests. First we create a mock LifecycleOwner using whatever mocking library you want (I’m using mockk). Then pass that into a LifecycleRegistry, which extends Lifecycle. This is a class that is used by Android’s Fragment class (and others) to update the lifecycle state. In our case, we are going to attach the ViewModel to a stubbed LifecycleRegistry in order to test our events.


@Test
fun testOnStart() {
val myViewModel = MyViewModel(mockRepository)
val mockLifeCycleOwner: LifecycleOwner = mockk()
val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
lifecycleRegistry.addObserver(myViewModel)
// Trigger the Lifecycle.Event
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
// Test the results how you want
verify { mockRepository.reloadData() }
}

Great! Now the ViewModel is observing changes. Now since we will be wanting to test lifecycle events quite often, let’s go ahead and make a reusable extension function in our test directory that will work for any LifecycleObserver.

fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
val mockLifeCycleOwner: LifecycleOwner = mockk()
val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
lifecycleRegistry.addObserver(this)
lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}

And now all we have to do to do to test our ViewModel events is:

@Test
fun onStart_reloadData() {
viewModel.testLifecycleEvent(Lifecycle.Event.ON_START)
verify { mockRepository.reloadData() }
}

That’s it! Clean, reusable code with a ViewModel that observes lifecycle.

--

--

jeffreysoboe

Jeff Padgett is an Android Developer at Accenture, Digital Products.