Better handling states between ViewModel and Composable

Igor Escodro
ProAndroidDev
Published in
4 min readMar 11, 2021

--

Jetpack Compose is right around the corner. After the recent announcement that it is now in Beta, now is a good moment to start learning this great new framework. And to do so, I decided to rewrite my personal application, Alkaa using it.

My first decision was to get rid of LiveData and starting using the new observables available in Kotlin. Once the repository returns a Flow<List<Task>>, the first attempt was to use the StateFlow, which at a first glance seems like a great alternative for LiveData.

The updated ViewModel and Composable from LiveData to StateFlow were:

And it works! Every time my repository is updated, it reflects the changes in my StateFlow and my Composable is updated!

But after some debugging, I noticed that the StateFlow keeps being notified and emitting new events forever, even if the Composable is no longer visible. And it does make sense once the documentation state:

A state flow is a hot flow because its active instance exists independently of the presence of collectors.
[…]
State flow never completes

So, I went to kotlinlang Slack and asked for help.

https://kotlinlang.slack.com/archives/CJLTWPH7S/p1614345603389700

After some great discussions, Adam Powell gave some great insights about how to deal with States using Jetpack Compose and alternatives were raised.

1. Using StateFlow with .stateIn()

If we want our flow to still hot, it is possible to use StateFlow with stateIn() function. It converts a regular cold Flow in a hot StateFlow that is started in the given CoroutineScope and SharingStarted strategy.

The updated code with this approach is:

In the code above the StateFlow is started in the viewModelScope and the strategy is WhileSubscribed, which means that “Sharing is started when the first subscriber appears, immediately stops when the last subscriber disappears”.

However, as is mentioned by the excellent article by John O’Reilly we still can face some issues because the StateFlow is in fact a hot flow. The ViewModel scope can stay alive for some time after the screen is no longer visible, which will keep emitting values for it. Specially if the Flow is created in the ViewModel init{} block.

Then, there is a second approach for the same issue.

2. Using a cold flow instead

If most of our problems in this basic scenario is because our Flow is hot, why does not make it cold? On this approach we expose a cold Flow from our ViewModel to our Composable and it collectAsState.

That’s it. Now we have a cold Flow that only will emit and be observed when the current Composable is being display in the screen. Creating a cold observable in this scenario really keeps the communication without loose ends.

After some tentatives, I adopted the second approach in my project because it felt simple and clean. I also decided to update the observables that does not return a Flow to this approach as well.

The LoadTaskUseCase returns a suspend fun of a single task and it is used to load the details screen. When the function returns, the task is emitted via sealed class to the Composable.

Icing on the cake

Now that we have our Flow better structured, a nice practice is to avoid multiple invocations after each recomposition. To do this, we use the remember function in our state. The implementation for it is:

Now our state will only re-triggered if the ViewModel changes, keeping only a single subscription not mattering how many times recomposition is called.

Final thoughts

At the moment, everything is new for all developers in Jetpack Compose and naturally we will face some difficulties. Handling states and observables is a hard task in any framework, and Compose is no exception to it.

I decided to write this article because the discussion that we had on Slack was very enlightening, help me a lot and I would like to share the findings with our community. Feel free to suggest alternatives and enhancements to these approaches.

For the complete discussion on Slack, please access the link below.

The kotlinlang Slack is a great channel to ask for help, specially because how new Compose is. There are a lot of people glad to help us! ☺️

If you want to dig deeper in the code I shared, this is the Pull Request where all the StateFlow were replaced to Flow in my personal project and fixed the issues mentioned earlier.

Special thanks for Adam Powell and all the developers that joined and contributed to this amazing discussion. ❤️

Edit: On March 24, 2021, Google released a new update for the library lifecycle-runtime-ktx, which addresses the issues presented on this article. Although the solutions here shown still valid, it’s recommended to take a look on the article by Manuel Vivo to better understand the Google official support for it.

--

--

Passionate Android developer | Google Developer Expert for Android