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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Lifecycle-Aware Components Using Android Jetpack
30 mins
- Getting Started
- Registering for the spoonacular API
- Lifecycles in Android
- Reacting to Lifecycle Changes
- Using Lifecycle-Aware Components
- Creating a Lifecycle Observer
- Lifecycle Events and States
- Events
- Reacting to Lifecycle Events
- States
- Using Lifecycle States
- Subscribing to Lifecycle Events
- Who Owns the Lifecycle?
- Using ProcessLifecycleOwner
- Creating a Custom Lifecycle Owner
- Adding Events
- Reacting to Events
- Testing a Lifecycle-Aware Component
- Setting Up The Tests
- Adding The Tests
- LiveData: A Lifecycle-Aware Component
- Creating and Assigning LiveData Variables
- Observing LiveData Changes
- Where to Go From Here?
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:
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:
- First, set and notify the lifecycle state.
- Then, verify that
init()
executed inNetworkMonitor
.
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
.