LiveData with Coroutines and Flow — Part III: LiveData and coroutines patterns
This article is part III of a summary of the talk I gave with Yigit Boyar at the Android Dev Summit 2019.
Part II: Launching coroutines with Architecture Components
Part III: LiveData and Coroutines patterns (this post)
ViewModel patterns
Let’s look at some patterns that can be used in ViewModels, comparing LiveData and Flow usages:
LiveData: Emit N values as LiveData
If we don’t do any transformations, we can simply assign one to the other.
Flow: Emit N values as LiveData
We could use a combination of the liveData
coroutine builder and collect on the Flow (which is a terminal operator that receives each emitted value):
But since it’s a lot of boilerplate, we added the Flow.asLiveData()
extension function, which does the same thing in one line:
LiveData: Emit 1 initial value + N values from data source
If the data source exposes a LiveData, we can use emitSource to pipe updates after emitting an initial value with emit:
Flow: Emit 1 initial value + N values from data source
Again, we could do this naively:
But if we leverage Flow’s own API, things look much neater:
onStart
sets the initial value and doing this we just need to convert to LiveData once.
LiveData: Suspend transformation
Let’s say you want to transform something coming from a data source but it might be CPU-heavy so it’s in a suspend function.
You can use switchMap
on the data source’s LiveData and then create the coroutine with the liveData
builder. Now you can just call emit on each result received.
Flow: Suspend transformation
This is where Flow really shines compared to LiveData. Again we can use Flow’s API to do things more elegantly. In this case we use Flow.map
to apply the transformation on every update. This time, because we’re already in a coroutine context, we can call it directly:
Repository patterns
Not much to say about repositories, as if you’re consuming a Flow and exposing a Flow, you just use the Flow API to transform and combine data:
Data source patterns
Again, let’s make the distinction between a one-shot operation and a Flow.
One-shot operations in the data source
If you’re using a library that supports suspend functions, like Room or Retrofit, you can simply use them from your suspend functions!
However, some tools and libraries do not support coroutines yet and are callback-based.
In that case you can use suspendCoroutine
or suspendCancellableCoroutine
.
(I don’t know why you would want to use the non-cancellable version though, let me know in the comments!)
When you call it, you get a continuation
. In this example we are using an API that let us set a complete listener and a failure listener so in their callbacks we call continuation.resume
or continuation.resumeWithException
when we receive data or an error.
It’s important to note that if this coroutine is cancelled, resume
will be ignored so if your request takes a long time the coroutine will be active until one of the callbacks is executed.
Exposing Flow in the data source
Flow builder
If you need to create a fake implementation of a data source or you just need something simple you can use the flow
constructor and do something like this:
This code emits a weather condition every two seconds.
Callback-based APIs
If you want to convert a callback-based API to flows, you can use callbackFlow
.
It looks daunting, but if you split it apart you’ll find that it makes a lot of sense.
- We call offer when we have a new value
- We call close(cause?) when we want to stop sending updates
- We use awaitClose to define what needs to be executed when the flow is closed, which is perfect for unregistering callbacks.
In conclusion, coroutines and Flow are here to stay! But they don’t replace LiveData everywhere. Even with the very promising StateFlow (currently experimental) we still have the users of the Java Programming Language and Data Binding to support, so it won’ t be deprecated for a while :)
Some links if you want to read more:
- Sean’s post series on coroutines
- Manu’s lessons learned migrating the Android Dev Summit to coroutines (including Flow)
My other blog posts about LiveData
- https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04
- https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7
- https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
- https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54