Converting LiveData to Flow: Lessons learned

Photo: Robert Lukeman from Unsplash

Android KTX provides opinionated methods, such as, facilitating using Kotlin Coroutines in Android Architecture Components. Make sure to understand their behaviors underneath before using them!

The official documentation on Kotlin coroutines on Android is quitewell-written, while there are numerous article posts covering the basics. In this story, we will focus on the lessons learned when using these extension methods, using as an example.


Let’s start with a concrete example using the orthodox Android App Architecture: Repository + ViewModel + Fragment

Repository Layer
  • is a simple Retrofit service.
  • We also added logging statements inside the flow collecting block.
ViewModel Layer
  • We use to convert the flow into a LiveData.
Fragment Layer
  • In the fragment layer, we added logging statements for the fragment lifecycle calls and used to simulate complex layout inflation.

Lesson I: Flow collection is re-executed after rotating the screen

When we run the app, it looks fine. However, after rotating the device screen to recreate the fragment, we can observe more logs like indicating the flow is executed.

2021-04-02 16:41:32.246 I: Fragment onCreate...
2021-04-02 16:41:32.247 I: Fragment onCreateView...
2021-04-02 16:41:32.754 I: Fragment onStart...
2021-04-02 16:41:32.755 I: flow running...
2021-04-02 16:41:32.756 I: Fragment onResume...
2021-04-02 16:41:33.309 I: Observer called...

That is weird! The original intention of ViewModel is to persist data that can survive UI recreation. After we rotate the device screen, the data should be retrieved from the storage. Contrarily, what we saw is that the data is fetched again from the service, which may cause unnecessary network calls or UI flicker.

Now let’s take a look at the method documentation of :

...If the LiveData becomes inactive (LiveData.onInactive) while the flow has not completed, the flow collection will be cancelled after timeoutInMs milliseconds unless the LiveData becomes active again before that timeout (to gracefully handle cases like Activity rotation).After a cancellation, if the LiveData becomes active again, the upstream flow collection will be re-executed.If the upstream flow completes successfully or is cancelled due to reasons other than LiveData becoming inactive, it will not be re-collected even after LiveData goes through active inactive cycle.

Our flow should complete successfully before rotation. This means that it will not be recollected even after LiveData goes through active-inactive cycle. But why do we still observe recollection?

The bug in our code is that the LiveData is not preserved as a property. If we create a new LiveData instance every time, the new LiveData will not know that the previous flow collection completes successfully.

Fix: remove

val reddits: LiveData<RedditListing>
get() = redditRepository.topReddits.asLiveData(context = Dispatchers.IO)


val reddits: LiveData<RedditListing> =
redditRepository.topReddits.asLiveData(context = Dispatchers.IO)

Learning II: Flow collection starts after onStart()

If we inspect the logcat again, we can see that flow collection does not start until . This is inconsistent with what we in , why is it so?

Let’s go back to the method documentation again:

Creates a LiveData that has values collected from the origin Flow.The upstream flow collection starts when the returned LiveData becomes active (LiveData.onActive).

Since is triggered when the lifecycle owner enters the state, this behavior is consistent with the Android Architecture Components.

Time series diagram using Flow.asLiveData()

If we want to eagerly trigger the flow collection before onStart(), then we probably need to leverage extension to launch the flow. With this, the flow collection could be brought ahead of time as early as ViewModel creation.

Fix: use viewModelScope

Now without the KTX extension methods, we need to manually update the LiveData inside function.

ViewModel Layer using viewModelScope

With this change, we can move the flow collection to when we initialize the ViewModel (In our example, in onCreateView()).

Time series diagram using ViewModelScope

Take away

is a convenient method with opinionated implications:

  • It cooperates with cancellation and remembers if the flow was completed or canceled. This may lead to trivial bugs like not preserving the Flow execution after activity recreation.
  • It binds Flow to the lifecycle of LiveData. This may indicate a performance limitation for eager data loading.

Android Developer@LinkedIn

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store