Lifecycle for Android Developers (Part 3)

Dinorah Tovar
6 min readApr 12, 2022

Under the hood of LifecycleOwners, collection, and events

Here is the list of the blogs in this series:

Flows

This is the third part of this series “Lifecycle for Android Developers” in the past part of this series, we discuss how Flow works, but we end up talking about the pitfall of memory consumption when this Flow is not lifecycle aware — making this dangerous.

The problem

The main problem of Flows (precisely about Hot Flows) is that they maintain an active state till they get disposed— this error used to be quite regular with Coroutines at the beginning, also on RxJava. Not disposing of your collectors can cause memory leaks and waste of resources.

Why? Well, the producers remain active, when the coroutine that triggers the flow collection suspends as the View goes to the background when usually should when the view is OnResume

Some developers end up using launchWhenStarted, launcheWhenResumed, launchedWhenX, thinking this will solve the underlying issue of Flow, however, this solution was not even proper — The proper and correct way of using it ends up being something like this:

This will end up in a manual process to stop the collection, now imagine doing this for all the fragments your application may have — this probably is not the solution we may want, this also applies for cold flows backed by a channel or with operators that are using conflated values like buffer or shareIn.

The solution we need is quite different, we need to make Flows lifecycle aware, subscribers need to be launched and run once the view is created, canceled when a view is destroyed (or goes to a Specific state in the nodes of the graph of a lifecycle) and automatically get relaunched when need it — without affecting the Kotlin first segmentation that Flow has — keeping the Flows as pure Kotlin.

The Solution

There’s a simpler solution for this in a library you may already be using:

Inside this library there are two suspend functions that can make sure the Flow is consumed by the subscribers in a clean and successful way:

These two elements — works differently for different scenarios, when some of us may think they look the same, in reality, they do a little thing differently, first things first, let's see how the Flow collection looks like inside a Fragment

Remember that every time we inside a Fragment our owner will be viewLifecycleOwner — we can not use this inside a Fragment, but we can do it for an Activity — this also applies for trying to access repeatOnLifecycle and the reason is that, repeatOnLifecycle is an extension function

The extension function repeatOnLifecycle is a suspend function that receives two parameters

  • A State — that can be something like Lifecycle.State.RESUMED
  • A Block scope — where it will run the collection

Inside the function — we can notice a code that looks quite similar to the one that we review on LiveData — however, is quite complex

For instance, this function does not need to check and assert the Main Thread like the one in LiveData.
As we mention in the first part of this series, the collection of the subscriber should not happen in States like Destroyed or Initialized, they need to move to upper levels in the nodes in the graph of the lifecycle. After this — we notice a Coroutine Scope, this scope is needed to maintain the context since we are gonna move to the Main Dispatcher to make sure we can touch the Lifecycle State in our nodes.

The variables created as the launchedJob and the observer, will let us attach the coroutines to the registered observer, we move to a suspension state of the original coroutine, then re-launches the block when the lifecycle moves in and into a new coroutine.
We also notice two functions we review in the first part of this serial Lifecycle.Event.upTo(State) and Lifecycle.Event.downTo(State)this makes the functions we review in the first part of this serial, so important, they work like a State Machine to assure the location of the lifecycle and the process that needs to happen as soon we reach or leave some specific state.
For example — If we call repeatOnLifecycle with a State Lifecycle.State.RESUMED — the state of the collection will start when the View is OnResume — however, the cancellation will go when the View goes to OnPause (not onDestroyed or OnStop)

And with this function, we can make sure the Flows will get a proper and secure awareness of the lifecycle! But there’s still another fuction we may one to review — flowWithLifecycle

The difference between repeatOnLifecycle and flowWithLifecycle is quite obvious once you look at the functions, one is an extension of the LifecycleOwner, and the other start with an extension to Flow, however, this last one uses repeatOnLifecycle under the hood, and emits items and cancels the underlying producer when the Lifecycle moves in and out of the target state, however, there's something different flowWithLifecycle use callbackFlow which is a type Cold Flow

A cold flow is a type of flow that executes the producer block of code on-demand when a new subscriber collects, which means that the block is called every time a terminal operator is applied to the resulting flow.

Internally, callbackFlow uses a channel, which is conceptually very similar to a blocking queue and has a default capacity of 64 elements.

The Thing about Jetpack Compose

For Compose, we can use Flow.collectAsState function, but there are some issues, especially because, Compose works different than a regular view, Compose does not recompose UI when the fragments go from one State to another, however, the flow producer may still be active and we may end up having the same issue that the one with pure StateFlow and SharedFlow

This is all for this part of the series. For the next part, we will discuss Flows and Jetpack Compose!

If you need help, I’m always happy to help, you can find me here:
Medium as Dinorah Tovar
Twitter as
@ddinorahtovar
StackOverflow as
Dinorah Tovar

--

--

Dinorah Tovar

Google Developer Expert on Android | Doing Kotlin | Making Software 24/7 | Kotlin Multiplatform | She/Her | Opinions are my own, and not my employer