[MUSIC PLAYING]
MANUEL VIVO: Hi, everyone.
I am Manuel Vivo.
In this talk, I'll cover why you should collect flows
in a lifecycle-aware manner in the UI
and how to use their repeatOnLifcycle,
flowWithLifecycle, or collect a state with lifecycle APIs
to do so.
Coroutines and flows are the recommended way
of doing asynchronous work on Android.
In an Android app architecture, flows
are used to communicate state changes over time.
Flows are exposed and transformed
through the hierarchy until they reach the UI, where
they are usually collected.
Typically, a view model exposes a state flow to the UI
by combining flows from the domain or data layers.
Once in the UI, the flow should be collected
in a lifecycle-aware manner.
Here you can see the typical lifecycle events
that happen in the Android UI.
In the light blue areas, the lifecycle
is at least started, which means the app is visible to the user.
Let's say we are building an app that shows the user
location on the screen.
Collecting in a lifecycle-aware manner
means collecting flows when the UI is visible
and stopping collection when it is not--
for example, when the app is sent to the background.
In green on the lifecycle pyramid,
you can see when the flow is being collected.
No more location updates are received when
the app is in the background.
This allows the app to save system resources when
the information isn't displayed on the screen-- for example,
by closing a WebSocket or stopping a GPS location
provider.
Flow, however, is a platform-agnostic Kotlin API
that isn't aware of the Android lifecycle.
If you collect for the entire lifecycle,
the flow collection will be active by default
until the UI is completely destroyed,
even when the app is in the background.
This wouldn't allow the rest of the app to optimize resources
if needed.
What's even worse is that updating
the UI when it is in the background
is dangerous because you could throw exceptions in some cases.
OK, we've got work to do now.
Let's see how we can collect in a lifecycle-aware manner.
Nothing prevents you from manually starting the flow
collection when the UI is started
and stopping when it is stopped, like here
with the uiStateCollectionJob variable.
That's fine.
However, doing this manually can be error prone in activities
and even more in fragments, where
we must use a default lifecycle observer
in the viewLifecycleOwner to follow
the current best practices.
We can make this easier.
The Android X lifecycle libraries
provide some APIs that do most of the work for you.
The lifecycle-runtime-ktx artifact
offers the repeatOnLifecycle API,
an extension function on lifecycle and lifecycleOwner.
And it also offers a flow operator
called flowWithLifecycle.
The lifecycle-runtime-compose artifact
brings the collectAsStateWithLifecycle
composable function, available as an extension function
on both Flow and StateFlow.
Let's cover them in detail.
repeatOnLifecycle is a suspend function
that takes a lifecycle state and a suspend block as parameters.
In its block, you can collect the flow.
To start and stop collecting when the UI is visible or not
on the screen, we pass in the Lifecycle STARTED state.
This works because repeatOnLifecycle automatically
creates a new coroutine that executes the block when
the lifecycle reaches that state and cancels
the coroutine running the block when it falls
below that lifecycle state.
In green, you can see the flow is being collected.
If you pass in the RESUMED state,
the flow collection stops when the lifecycle post
event occurs.
As repeatOnLifecycle is a suspend function,
you need to call it inside a coroutine.
In Activities, you can launch one using the lifecycleScope
in the onCreate function.
As a fragment and its build have different lifecycles,
make sure to use the viewLifecycleOwner
when collecting flows in fragments
in the onViewCreated function.
repeatOnLifcycle is a suspend function
that will return only when the lifecycle is destroyed.
So usually, you shouldn't put any code after it.
An alternative to repeatOnLifecycle
is the flowWithLifecycle flow operator.
It emits values from the upstream flow
when the lifecycle is at least in a certain lifecycle state.
Start it by default. When it falls below the state,
the upstream flow collection is canceled.
In a similar way to repeatOnLifecycle,
we use this operator before collecting
from the flow in the onCreate function of an activity
or onViewCreated if using fragments.
If we take a look at the implementation details
of this API, you can see it uses the repeatOnLifecycle API
to start or stop collecting the upstream flow,
giving a mean active state passed as a parameter.
It creates a flow using callbackFlow
to emit the collected items until the lifecycle is
destroyed.
Then the flow completes by closing its backend channel.
When it comes to Jetpack Compose,
we offer an API which is currently experimental
and in an alpha version.
collectAsStateWithLifecycle is a composable function
that collects values from a flow and represents
the latest value as Compose state
in a lifecycle-aware manner.
Use it in a composable function of a particular screen
like this.
Since it returns Compose state, every time a new flow
emission occurs, the value of this state
object updates, triggering a recomposition
where the value is read.
Under the hood, this API also uses repeatOnLifecycle.
In this case, the lifecycles are where
collected values are used as the source of data for the Compose
produceState API.
And that's pretty much it.
Remember to collect flows in a lifecycle-aware manner
in the UI.
When working with the view system,
you can use repeatOnLifecycle and flowWithLifecycle.
And when working with Compose, you
can use collectAsStateWithLifecycle
or the other APIs.
To learn more about this topic, take a look
at these blog posts that cover these APIs in depth.
Hope you enjoyed this talk, and see you next time.
Bye.
[MUSIC PLAYING]