Comparing use of LiveData and StateFlow in a Jetpack Compose project28 Nov 2020 Share on:
I recently updated PeopleInSpace project to make use of Kotlin Flow in shared multiplatform
code to poll for the position of the International Space Station (ISS). As part of that work I had initially updated
the Android app to use
StateFlow (along with
stateIn()) instead of
LiveData. However there were some lifecycle implications of this change
when used alongside Jetpack Compose that have caused me to revert, for now, to using
LiveData. This article is intended to capture results of that comparison.
Note that this is based on Jetpack Compose
I should also point out that it was following interaction on Twitter that helped trigger realisation about some of these differences.
As much as I love Kotlin flows, I'm still sticking to LiveData for now. From undesired view behavior when working with hot flows to the tricky difference between `launchIn` and `launchWhen` functions when working with lifecycle scopes, I choose LiveData's safety and simplicity.— Fred Porciúncula (@tfcporciuncula) November 19, 2020
So, to start off with, the following is the implementation of code in
PeopleInSpaceRepository that returns a
flow that performs that polling.
That then had been converted to
PeopleInSpaceViewModel as follows.
And then in Jetpack Compose code we convert this to
At this point, even in a non-Compose application, there are lifecycle implications of observing/collecting
StateFlow in UI code (when compared to use of
LiveData). These differences are very well captured
in this article including, in particular:
LiveData.observe() automatically unregisters the consumer when the view goes to the STOPPED state, whereas collecting from a StateFlow or any other flow does not.
Advice in that article then is to use
launchWhenStarted to collect the flow, so that the coroutine that triggers the flow collection
suspends when the activity goes to the background. However, as noted, the underlying producers still remain active in this case.
With hot implementations, be careful when collecting when the UI is not on the screen, as this could waste resources. You can instead manually stop collecting the flow.
So, to avoid that we need to to explicitly cancel in
onStop() (and restart collection in
onStart()) as shown here:
When using Compose, and in particular
collectAsState(), there aren’t any options to influence lifecycle behaviour like this. Under the hood
collectAsState makes use of
LaunchedEffect and as such the collection will only be cancelled when the composition is disposed (which doesn’t happen
when we go in to background and underlying activity goes in to STOPPED state). Therefore the “producer” (the flow created by
will continue to keep polling. BTW there’s nice overview of Jetpack Compose’s effect handlers, like
LaunchedEffect, in this article.
Converting to use
So, the change that was made then was to make use of
Flow extension function)
The documentation for
asLiveData includes following key points.
- upstream flow collection starts when the returned LiveData becomes active.
- if the LiveData becomes inactive the flow collection will be cancelled.
- after cancellation, if the LiveData becomes active again, the upstream flow collection will be re-executed.
Now, when the app goes in to background,
issPosition will become inactive
and flow collection (and associated polling) will be cancelled. It will then be restarted if/when app comes back in to the foreground.
Our Compose code then was updated then to use
I also made similar changes to these (for same reason) to BikeShare project.
There have seemingly been some discussions in Compose team around changing default behaviour for
onStop() so that may have impact on what’s
been described here. For example these LifecycleCoroutineScope Cancelling APIs
changes look like they could potentially be used to provide this behaviour.
This CL adds LifecycleCoroutineScope#launchUntilX APIs that cancel the block when the event X comes as opposed to the launchWhenX APIs that suspend the block instead.
Update 24th March 2021: This has now been addressed with new
Flow.flowWithLifecycle() api (as described here)
Featured in Android Weekly Issue #442
Wrote short article about switch in PeopleInSpace Jetpack Compose client from using StateFlow to asLiveData(). I think this is evolving area and sounds like some changes are being considered for Compose that might yet affect this behaviour.https://t.co/qsE4PUijnr— John O'Reilly #BlackLivesMatter (@joreilly) November 28, 2020