Lifecycle-Aware Components Using Android Jetpack

Learn about lifecycle-aware components including what they are, how they work, how to implement your own components and how to test them. By Rodrigo Guerrero.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Using ProcessLifecycleOwner

In some cases, a component needs to react to the application lifecycle changes. For example, it might need to track when the user sends the app to the background and when it comes back to the foreground. For those cases, Jetpack provides ProcessLifecycleOwner, which implements the same LifecycleOwner interface.

This class provides a lifecycle for the entire app process. It dispatches ON_CREATE only once, when the app starts for the first time. It won’t dispatch ON_DESTROY at all.

ProcessLifecycleOwner dispatches ON_START and ON_RESUME when the first activity in the app goes through these states. Finally, ProcessLifecycleOwner dispatches ON_PAUSE and ON_STOP events after the last visible activity of the app goes through them.

It’s important to know that these last two events will happen after a certain delay. There’s a delay because ProcessLifecycleOwner needs to be sure of the reason for these changes. It only needs to dispatch these events if they happen because the app went to the background and not due to configuration changes.

When using this lifecycle owner, a component needs to implement LifecycleObserver, as usual. Open AppGlobalEvents.kt in the analytics package. You can see that it’s a LifecycleObserver.

Its functionality is to track whenever the app comes to the foreground or goes to the background. As you can see in the code, this happens when the lifecycle owner sends the ON_START and ON_STOP events.

You need to register this LifecycleObserver in a different way. Open RecipesApplication.kt and add the following code in onCreate():

  ProcessLifecycleOwner.get().lifecycle.addObserver(appGlobalEvents)

With this code, you get an instance of ProcessLifecycleOwner and add appGlobalEvents as its observer.

Now, build and run the app. After the app starts, send it to the background. Open Logcat, filter by the tag APP_LOGGER and you’ll see the following messages:

AwarenessFood Logcat messages

So far, you’ve seen how fragments, activities and application components implement the LifecycleOwner interface. But that’s not all. You’ll now see how to create custom classes that do the same thing.

Creating a Custom Lifecycle Owner

Remember that any class can implement the LifecycleOwner interface. This means that you can create your own lifecycle owner.

You’ll create a custom lifecycle owner that starts its lifecycle when the device loses the network connection and ends its lifecycle whenever it recovers connectivity.

Open UnavailableConnectionLifecycleOwner.kt in the monitor package and modify the class to implement LifecycleOwner:

@Singleton
class UnavailableConnectionLifecycleOwner @Inject constructor() : LifecycleOwner {
// ...
}

Next, add the following LifecycleRegistry to UnavailableConnectionLifecycleOwner as follows:

private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)

override fun getLifecycle() = lifecycleRegistry

This LifecycleRegistry is an implementation of Lifecycle that can handle multiple observers and notify them of any changes in the lifecycle.

Adding Events

You can use handleLifecycleEvent() to notify whenever lifecycle events occur. Add the following methods to the class:

fun onConnectionLost() {
  lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}

fun onConnectionAvailable() {
  lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}

In this case, whenever the device loses the connection, lifecycleRegistry will send the ON_START event to its observers. It will send ON_STOP whenever the connection becomes available.

Finally, add the following code:

fun addObserver(lifecycleObserver: LifecycleObserver) {
  lifecycleRegistry.addObserver(lifecycleObserver)
}

addObserver() will register any observer that wants to receive UnavailableConnectionLifecycleOwner lifecycle events.

Reacting to Events

Now, open MainActivity.kt and add the following code in onCreate():

unavailableConnectionLifecycleOwner.addObserver(networkObserver)

This will make networkObserver react to unavailableConnectionLifecycleOwner events. NetworkObserver will show the snackbar whenever the device loses the connection and hide it whenever the connection is back.

Finally, replace handleNetworkState() with:

private fun handleNetworkState(networkState: NetworkState?) {
  when (networkState) {
    NetworkState.Unavailable -> unavailableConnectionLifecycleOwner.onConnectionLost()
    NetworkState.Available -> unavailableConnectionLifecycleOwner.onConnectionAvailable()
  }
}

This code will trigger unavailableConnectionLifecycleOwner‘s lifecycle events.

Finally, build and run the app. It works as before, except that the app now uses a custom lifecycleOwner to handle network events.

How do you write tests for lifecycle-aware components? You’ll learn that in the next section.

Testing a Lifecycle-Aware Component

Another benefit of having all lifecycle-aware code in NetworkMonitor is that you can test the code according to the lifecycle event that it needs to react to.

These tests will verify that the correct method within NetworkMonitor executes according to the lifecycle owner’s state. Building the test will be similar to the steps you followed when implementing a custom lifecycle owner.

Setting Up The Tests

Open NetworkMonitorTest.kt. To start, the test needs to mock both a lifecycle owner and the network monitor. Add the following two mocks right after the class’s opening braces:

private val lifecycleOwner = mockk<LifecycleOwner>(relaxed = true)
private val networkMonitor = mockk<NetworkMonitor>(relaxed = true)

In the code above, mockk is a function provided by the MockK library that helps you create mocked implementations of a certain class. The relaxed attribute specifies that you can create mocks without specifying their behavior.

Next, add the following variable after the variables you just added:

private lateinit var lifecycle: LifecycleRegistry

This adds LifecycleRegistry to the test, which will handle the observers and notify them of changes in its lifecycle.

Finally, add the following to setup():

lifecycle = LifecycleRegistry(lifecycleOwner)
lifecycle.addObserver(networkMonitor)

This initializes the lifecycle registry, passing the lifecycle owner mock, and adds the NetworkMonitor so it can observe the lifecycle changes.

Adding The Tests

To verify that the correct method executes in NetworkMonitor, the lifecycle registry should set the correct lifecycle state and should notify its observers. You’ll use handleLifecycleEvent() to achieve this.

The first test you’ll write verifies that the ON_CREATE event triggers init().

Implement this using the code below:

@Test
fun `When dispatching On Create lifecycle event, call init()`() {
  // 1. Notify observers and set the lifecycle state.
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)

  // 2. Verify the execution of the correct method.
  verify { networkMonitor.init() }
}

In the code above, you:

  1. First, set and notify the lifecycle state.
  2. Then, verify that init() executed in NetworkMonitor.

Finally, add the following imports:

import androidx.lifecycle.Lifecycle
import io.mockk.verify
import org.junit.Test

Execute the test — and it’s successful.

Add the following code to test that the START lifecycle event calls registerNetworkCallback():

@Test
fun `When dispatching On Start lifecycle event, call registerNetworkCallback()`() {
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)

  verify { networkMonitor.registerNetworkCallback() }
}

This verifies that ON_START triggers registerNetworkCallback().

Execute the test and it will pass.

Finally, create a test to verify unregisterNetworkCallback(). In this case, the test needs to dispatch ON_STOP.

Use the following code to achieve this:

@Test
fun `When dispatching On Stop lifecycle event, call unregisterNetworkCallback()`() {
  // 1. Notify observers and set the lifecycle state.
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)

  // 2. Verify the execution of the correct method.
  verify { networkMonitor.unregisterNetworkCallback() }
}

This verifies that ON_STOP triggers unregisterNetworkCallback().

Run the test and.. it fails with this error: Verification failed: call 1 of 1: NetworkMonitor(#2).unregisterNetworkCallback()) was not called.

Does this mean that unregisterNetworkCallback() didn’t execute with the ON_STOP event? The test is trying to dispatch the ON_STOP event, but according to the lifecycle flow, at least ON_START should occur before ON_STOP.

Now, try this approach. Add the code to dispatch ON_START first:

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)

Run the test — now, it’s successful.

As you can see, it’s really easy to test if the correct lifecycle events trigger the correct methods in NetworkMonitor.