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?
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 enum
s 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:
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 afterON_STOP
. -
STARTED: After
ON_START
and afterON_PAUSE
. -
RESUMED: Only occurs after the
ON_RESUME
. -
DESTROYED: After
ON_DESTROY
but right before the call toonDestroy()
. Once the state isDESTROYED
, 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.
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.