How To Collect Flows Lifecycle-Aware In Jetpack Compose

Why it’s important to consider the app’s lifecycle also in Jetpack Compose and how you can easily take care of it

Yanneck Reiß
ProAndroidDev

--

Time lapse photography of waterfalls
Photo by James Wheeler from Pexels

In a previous article, I talked about how to facilitate your everyday life as a developer when it comes to collecting Flows while considering the app’s lifecycle with the help of some handy extension functions.

However, that article focused only on handling the collection of Flows in the legacy XML way of defining UIs.

But the requirement to take care of the app’s lifecycle doesn’t stop with the usage of Jetpack Compose.

While composables have their own lifecycle, which leads finally to the requirement of converting our streams to State objects, we still need to take the actual app’s lifecycle when it comes to observing or, in the case of Flow objects, collecting streams.

Therefore, in this follow-up article, I want to share some useful helper functions that will allow you to easily collect Flow streams in your composables while automatically taking care of the app lifecycle.

1. Lifecycle-Awareness in Jetpack Compose

You may ask yourself the following:

“Why do we even need to consider the app’s lifecycle if composables have their own anyway?”

While it's true that composables have their own lifecycle for rendering the UI and maintaining their state, the app lifecycle still remains.

Just like in the legacy XML way of defining UIs, we still need to take care of cases for example in which the app goes to the background or simply just stops while still collecting streams.

As already mentioned, in Jetpack Compose we often don’t directly use the items of a stream but convert them into a State.

Because we still listen to streams that potentially can be triggered while the app is not present or remain over the app lifecycle without getting properly handled, we need to introduce a new toolkit of functions on how to handle these cases.

2. Collect a Flow in a composable

So how do we collect a Flow and convert into a State that we can use in our Jetpack Compose code?

Let’s assume that we have a Flow that comes from the data layer, for example from our Android Room Database, and gets forwarded through our ViewModel.

Finally, we want to display the list of example entities in our UI by collecting the Flow in our UI or more precisely in our composable.

Conventionally, we would achieve that by converting the Flow to a State object in our composable with the help of the Flow extension function collectAsState() like you can see in the following snippet:

As you can see we just need to call that function via the by delegate. We then no longer need to handle the underlying State because it directly gets evaluated to a list of entities.

We can then just pass the List to our content composable that is responsible for displaying the UI.

You will probably recognize that this approach is very easy to use and handy to manage. Unfortunately, if we use this without further do, there is a danger that the Flow will be collected even if the app runs in the background.

2. Collect a Flow lifecycle-aware in a composable

So how do we now achieve lifecycle-awareness? As I showed you in my mentioned previous article, we could try to use the repeatOnLifecycle() function. But that makes no sense for our use case at this point.

As Manuel Vivo stated in his article “A safer way to collect flows from Android UIs” we can use the Flow extension function flowWithLifecycle() to achieve the same requirements.

How he states, the function is implemented using the repeatOnLifecycle() function and therefore can manage the collection of the Flow while considering the state of the app’s Lifecycle.

So to use this function, we are no longer able to directly collect the Flow from our ViewModel but need to wrap it in an own remember statement before we can use our collectAsState() property delegation once again.

The code snippet above now shows our previous example but with the conversion of the Flow to be lifecycle-aware. We now invoke our collectAsState() function no longer directly on the Flow from our ViewModel itself but on the Flow called with the flowWithLifecycle(..) function.

The collection now only proceeds if the Lifecycle is at least in the Lifecycle.Started state.

But if you ask me, that is quite a lot of boilerplate code, don’t you think? Imagine you have not one but five or more Flow objects you first need to convert before you can safely collect them.

There must be a better way!

3. Extension functions to the rescue

Optimally, we would have the ability to use the presented collectAsState() function and automatically collect the respective Flow lifecycle-aware.

So let’s try to achieve that.

3.1 The Flow transformation

The transformation of the flow into a lifecycle-aware form is performed in the same way for each flow. So here is our first approach for improvement potential.

Take a look at the following code snippet and compare it with the transformation in the code snippet shown in section 2.

Do you recognize it? We now just take the Flow we want to transform, directly pass it as key1 to the remember function, and apply the flowWithLifecycle() function to it.

The Flow collection can now already be reduced to the following code snippet:

Very nice, we can now just use our rememberFlow() function with the respective Flow as input parameter and collect the produced Flow and are automatically lifecycle-aware.

But it’s still two instead of one line of code for every Flow collection. Let’s try to achieve our initial optimal requirement

“we would have the ability to use the presented collectAsState() function and automatically collect the respective Flow lifecycle-aware.”

3.1 The Flow collection

Every Flow collection will need the step to use our newly created rememberFlow function. So maybe we could just merge it with the collectAsState() function?

Consider the following code snippet:

If you step into the implementation of the collectAsState() extension function, you will recognize what we did here.

We just replicated and put our rememberFlow() function in front before we return the State we can then evaluate using the by delegate in our composable.

Take a look at the following example to see the result:

We did it!

We can now collect the Flow lifecycle-aware with just one line of code.

4. Bonus: Collect a StateFlow lifecycle-aware in a composable

If you try to use the presented collectAsStateLifecycleAware() extension function, you will see that even if you declared an initial parameter in the initialization of the MutableStateFlow, the collectAsStateLifecycleAware() still will need you to provide an initial parameter once again on the UI layer or to be more specific in the composable.

That is a little bit annoying and can lead to higher maintenance effort if you always need to take care of the initial value at two different places for the same StateFlow.

Let’s adapt the function, and convert it to an extension function specially for the StateFlow.

As you can see we once again use our collectAsStateLifecycleAware() function.

Since we are in the scope of the StateFlow, we can now access its value and set it directly as the initial value of the collectAsStateLifecycleAware() function and therefore as the initial value of the underlying produced State object.

Glossary

Finally, the following code snippet shows all presented helper and extension functions at a glance:

Update 17.07.2022

As time goes on, new capabilities arise. That’s why I’ve written a follow-up article on this topic in the meantime that you should check out!

Conclusion

Considering the lifecycle when it comes to observing or collecting states has always been quite a hustle.

Helper functions like repeatOnLifecycle or flowWithLifecycle on the other hand, provide some very handy methods to make our apps more robust regarding sudden changes in the state of our apps.

By using Kotlin’s Delegate properties and extension function we can use them to easily integrate them in our architecture and therefore in our daily development process.

Using the provided functions, there is no further boilerplate coded needed to achieve lifecycle-awareness when it comes to collecting Flows in your composables.

If you are already using the features I hope that the presented functions might help you to clean up your code a bit or otherwise inspire you to check your app for lifecycle-awareness.

I hope you had some takeaways, clap if you liked my article, make sure to subscribe to get notified via e-mail, and follow for more!

--

--

Follow me on my journey as a professional mobile and fullstack developer