Migrating from LiveData to Flow in 5 steps

Fedor Erofeev
5 min readNov 9, 2021

--

This is my first post on Medium (updated on 19/04/2021 with the correct way to collect Flow inside Fragments), but I feel it might be useful for someone looking for a simple guide to move on from LiveData to Kotlin Flow (especially that now StateFlow can be used with data binding).

First, why would you want to move from LiveData?

There are quite a lot of small improvements that separately don’t seem like worth refactoring, but once you put them all together they definitely justify the change. The biggest benefits:

  1. More powerful transformations. Transform, filter and do anything with the data.
  2. Can be combined. Combine as much flows as you need.
  3. Collectable from any dispatcher. No more need to set Dispatchers.Main after fetching data on asynchronously on Dispatchers.IO.
  4. Adjustable (replays, buffers, etc.). More flexibility on how to handle data and errors.
  5. Convertible from cold to hot (stateIn). We can easily convert a cold flow to StateFlow using stateIn(). Please, watch a video by Android Developers attached in the end of this post as it explains it really great!
  6. SingleLiveEvent made easy. (because we don’t want our error dialogs pop-up on each rotation) A Channel is a great solution that is convertible to a Flow with a simple extension function.
  7. Easier to test. No more getOrAwaitValue or observeForTesting.
  8. Better for multiplatform projects. No need to re-write code since they’re not tied to specific platform, unlike LiveData.

To learn more about the features of Flows and why it’s a good choice to move on from LiveData, please, look at this articles in the end of this post (especially the one by @JoseAlcerreca who has a very detailed article with in-depth explanations).

“Simple things are harder and complex things are easier” @JoseAlcerreca

Also, you will see some examples in this article where you will be able to see these benefits yourself. However, for the main part, I’ll try to show how easy and painless the transition can be.

Before you start, make sure you use the following dependency with at least version 2.4.0:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'

Let’s get started!

Step 1. Changing you BaseViewModel.

Since most probably you have SingleLiveEvent in it, let’s see how you can update it using Channel. Here is a simplified BaseViewModel:

There will be a little bit more code for the version with Flow, since we will use Coroutines to update the value and expose the properties as Flow (and this is the only place where code gets somewhat bigger!):

Step 2. Changing your BaseFragment.

Before we continue, let’s have a look at the correct way to collect Flow inside the Fragments (if you will use repeatOnLifecycle(Lifecycle.State.Started) you will get “The repeatOnLifecycle API should be used with viewLifecycleOwner” warning) Here is the correct solution provided by Google: launchAndRepeatWithViewLifecycle

So our Fragment will change to:

Step 3. Changing your ViewModel.

Let’s use a very simple example, like this one:

To replace MutableLiveData we will use MutableStateFlow, and expose it as StateFlow for our UI using asStateFlow():

So far, we’ve looked at simple things that are harder — now let’s look at more complex things that get so much easier!

Let’s start with a simple ViewModel for some RegistrationFragment, where you want to check if the user filled in all the fields before enabling the “Register” button.

LiveData offers us MediatorLiveData to observe multiple LiveData objects, however, it adds a lot of repetitive code — the more input fields you have, the longer and more complex will the validation become:

If you add more complex validation, the code will get even longer and hardly readable.

Using Flow you can just use combine() function and combine up to 5 Flow out-of-the-box or add an extension function to support more. And that’s it:

Exactly the same validation but simpler and cleaner!

In case you have database queries, you most probably usually use Transformations.switchMap(). However, even after transformation we cannot directly expose the list to our UI as we still need to transform it to domain model. This adds more properties to our code and the more transformations we have, the longer our code becomes:

Here we have 3 variables and multiple transformations. Let’s look how we can achieve the same result with flow with only 1 variable (switchMap analog for Flow is flatMapLatest — still ExperimentalCoroutinesApi)

With Flow we have only one variable and we can add as many transformations as we need.

We can also have additional suspend functions while transforming our data without any boilerplate. Feeling excited? Because I definitely am.

This way of using suspend function during transformations can be used for an additional Api calls (if you save some items but don’t save details and want to fetch every time), sub-queries, etc.

Let’s take a very simple example, when you want to save space on the device and save only favorite SomeItems in the database (even though it’s better to save them all, but we will assume it’s really important to save even that small space!)

Let’s assume there are 3 types of SomeItem in our application:
SomeItem — parsed from JSON
DomainSomeItem
-domain
DBSomeItem
-database

We need to check if the item parsed from JSON (SomeItem) was added to our favorite table (DBSomeItem). So when we are transforming SomeItem into DomainSomeItem to expose it to our UI, we need to find if we have the same item in our table of and show that it’s a favorite one.

Ready! As simple as that. These are some specific cases which you might need or might not, but it’s definitely worth having these super-powers once you’ll start adding functionality to your application.

Step 4. Observe the data inside your Fragment.

This will be exactly the same as for our BaseFragment.

Step 5. Update your Unit tests!

Probably, your test look similar to the code below:

And to update it for flow:

Not only you get rid of boilerplate code, pausing dispatcher, but also in cases with MediatorLiveData you will get rid of observeForTesting. There are many other functions for Flows like take() where you can pass the amount of items from Flow you want to test without needing to use a Job.

If you’re here, means you’ve read it till the end, so thank you for your time and hope it will be useful! To learn more about Flow and for better understanding how it works, there are amazing articles that helped me a lot preparing my application for transition:

--

--