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 2 of 4 of this article. Click here to view the first page.

Creating a Lifecycle Observer

A lifecycle observer is a component that has the ability to observe and react to the lifecycle state of its parent. Jetpack provides the LifecycleObserver interface to transform a class into a lifecycle observer.

It’s time to start upgrading NetworkMonitor. Open NetworkMonitor.kt and make it implement LifecycleObserver, like so:

class NetworkMonitor @Inject constructor(private val context: Context) : LifecycleObserver {
 // Code to observe changes in the network connection.
}

That’s it. Now, NetworkMonitor is a lifecycle observer component. You’ve completed the first step to convert it to a lifecycle-aware component.

Lifecycle Events and States

The Lifecycle class is responsible for knowing the lifecycle state of the parent and communicating it to any LifecycleObserver that’s listening.

Lifecycle uses two enums to manage and communicate the lifecycle state: Event and State.

Events

Event‘s values represent the lifecycle events that the operating system dispatches. The available values for Event are:

  • ON_CREATE
  • ON_START
  • ON_RESUME
  • ON_PAUSE
  • ON_STOP
  • ON_DESTROY
  • ON_ANY

Each value is equivalent to the lifecycle callback with the same name. ON_ANY is different because all lifecycle callbacks trigger it.

Reacting to Lifecycle Events

NetworkMonitor is now a LifecycleObserver, but it doesn’t react to any lifecycle changes yet. To make it do so, you need to add the @OnLifecycleEvent annotation to the method that will react to a specific lifecycle change. You use a parameter to indicate which lifecycle event it will react to.

Add @OnLifecycleEvent to the different methods, as follows:

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun init() {
// ...
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun registerNetworkCallback() {
// ...
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun unregisterNetworkCallback() {
// ...
}

In this case, NetworkMonitor needs its init() to react to the ON_CREATE event. registerNetworkCallback() reacts to the ON_START event and unregisterNetworkCallback() to ON_STOP.

Now that NetworkMonitor can react to lifecycle changes, you need to do some cleanup. You no longer need the following code in MainActivity.kt because NetworkMonitor now performs these actions itself.

override fun onCreate(savedInstanceState: Bundle?) {
  // ...
  // 1.Network Monitor initialization.
  networkMonitor.init()
  // ...
}

// 2. Register network callback.
override fun onStart() {
  super.onStart()
  networkMonitor.registerNetworkCallback()
}

// 3. Unregister network callback.
override fun onStop() {
  super.onStop()
  networkMonitor.unregisterNetworkCallback()
}

Remove onStart() and onStop() completely. Also remove the networkMonitor.init() line from onCreate().

By making these changes, you’ve moved the responsibility to initiate, register and unregister the component from the activity to the component itself.

States

State holds the current state of the lifecycle owner. The different available values are:

  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED
  • DESTROYED

These states are useful when an action in a lifecycle-aware component needs to know if a specific event has occurred.

One example is when a long operation runs during the ON_START event and the activity or fragment is destroyed before that operation completes. In this case, the component shouldn’t execute any action during the ON_STOP event, since it never completely initialized.

There’s a relationship between the events and the states of the lifecycle. The following diagram shows this relationship:

Lifecycle States and Events

Here’s when those states occur:

  • INITIALIZED: When the activity or fragment is already constructed but before onCreate() gets executed. This is the initial state of the lifecycle.
  • CREATED: After ON_CREATE and after ON_STOP.
  • STARTED: After ON_START and after ON_PAUSE.
  • RESUMED: Only occurs after the ON_RESUME.
  • DESTROYED: After ON_DESTROY but right before the call to onDestroy(). Once the state is DESTROYED, the activity or fragment won’t dispatch any more events.

Using Lifecycle States

Sometimes, components need to execute some code when its parent is at least in a certain lifecycle state. You need to ensure that NetworkMonitor executes registerNetworkCallback() at the correct moment.

Add a Lifecycle parameter in the constructor of NetworkMonitor, as follows:

private val lifecycle: Lifecycle

With this, NetworkMonitor has access to its parent lifecycle state through the lifecycle variable.

Now, wrap all the code in registerNetworkCallback() with the following condition:

fun registerNetworkCallback() {
  if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
    // ...
  }
}

With this condition, NetworkMonitor will only start monitoring the network connection when the parent lifecycle is at least in the STARTED state.

This comes in handy because the parent’s lifecycle can change before the code execution completes in the component. Knowing the state of the parent can help avoid crashes, memory leaks and race conditions in the component.

Finally, open MainActivity.kt and modify the creation of NetworkMonitor as follows:

networkMonitor = NetworkMonitor(this, lifecycle)

NetworkMonitor now has access to its parent’s lifecycle and will only start listening to network changes when the activity is in the right state.

Subscribing to Lifecycle Events

For NetworkMonitor to actually start reacting to lifecycle changes, its parent needs to know about its existence so it can dispatch its lifecycle events to the component.

In MainActivity.kt, add the following line in onCreate() after the line where you initialized networkMonitor:

 lifecycle.addObserver(networkMonitor)

This tells the lifecycle owner that NetworkMonitor will listen to its lifecycle events. If there are multiple components that need to listen to these changes, you can add them to the lifecycle owner as observers without a problem.

This is a great improvement. With only a single line, the component will now receive the lifecycle changes from its lifecycle owner. You don’t need boilerplate code in the activity anymore. Besides, the component itself holds all the initialization and configuration code, making it self-contained and testable.

Build and run the app again. After the recipe loads, put the device in airplane mode. You’ll see the network connection error snackbar appear, as it did before.

Making the snackbar lifecycle-aware. Network error showing in a snackbar.

Behind the scenes, however, you’ve vastly improved the implementation.

Who Owns the Lifecycle?

Everything looks great, but… who owns the lifecycle? Why does the activity own the lifecycle in this example? Are there any more owners?

A lifecycle owner is a component that implements the LifecycleOwner interface. This interface has one method that the owner needs to implement: Lifecycle.getLifecycle(). Basically, any class that implements this interface can be a lifecycle owner.

Android provides built-in components that are lifecycle owners. For activities, ComponentActivity, which is the base class for AppCompatActivity, is the one that implements LifecycleOwner.

However, there are other classes that already implement this interface, too. For example, Fragment is a LifecycleOwner. This means that you can move this code to a Fragment, if needed, and it will work the same way as in MainActivity.

The lifecycle of fragments can be considerably longer than the lifecycle of the view they contain. If an observer interacts with the user interface in a fragment, this can cause a problem because the observer can modify a view before it’s initialized yet or after it’s destroyed.

That’s why you can find a viewLifecycleOwner within Fragment. You can start using this lifecycle owner during onCreateView() and before onDestroyView(). Once the view lifecycle gets destroyed, it won’t dispatch any more events.

A common use of viewLifecycleOwner is to observe LiveData in a fragment. Open FoodTriviaFragment.kt and, in onViewCreated(), add the following code right before viewModel.getRandomFoodTrivia():

viewModel.foodTriviaState.observe(viewLifecycleOwner, Observer { 
  handleFoodTriviaApiState(it)
})

You need to add this import too: import androidx.lifecycle.Observer.

With this code, FoodTriviaFragment will react to foodTriviaState, which is a LiveData. Since the observer has viewLifecycleOwner as its owner, it will only receive events while the fragment is in an active state.

It is time to build and run the app. Tap the More menu option and select Food Trivia. Now you are able to get some fun and interesting Food Trivia in your app.