LiveData transformations

Peter Törnhult
ProAndroidDev
Published in
4 min readJun 3, 2020

--

LiveData is a great tool to use when working with Android Architecture Components. Before I knew how to use the Transformations class, I was abusing LiveData and creating a lot of code-smell. Having worked with LiveData and Architecture components for a few years now I think I’ve figured out some good practices and patterns that I’d like to share with you.

The basics…

Transforming LiveData is pretty easy to do and there is an awesome helper class named Transformations for just this purpose. This class provides three static methods: map, switchMap and distinctUntilChanged which will be explained below. All of the examples below will use the following data class, which represents a player we receive from our database or backend API. This Player model just has a name and score field to make it easier for the examples, but in reality, it would have many more fields.

map

Transforms the value of LiveDatainto another value. Here’s a simple example of how this can be used:

switchMap

Transforms the value of a LiveDatainto another LiveData. switchMap transformations can be a bit tricky, so let’s start with a simple example: We want to implement a basic search feature for players. Every time the search text changes we want to update search results. The following code shows how that works.

distinctUntilChanged

Filters LiveDataso that values will not be emitted unless they have changed. Many times we might be notified about a change that doesn’t contain any relevant changes. If we’re listening for the names of all players, we don’t want to update the UI when the score changes. This is where the distinctUntilChanged method is useful.

This is a really neat feature and I tend to use it quite often in my code. For my use-cases, it’s mostly related to RecyclerView/ adapter updates.

livedata-ktx extensions for Transformations

All of the Transformations class functions above are also available as extension functions on LiveData using the dependency:

androidx.lifecycle:lifecycle-livedata-ktx:<version>

With this you can, for instance, rewrite the example above as:

Behind the scenes of the Transformations class

We’ve just covered 3 simple transformations that you can actually write your self. All of them are written using the MediatorLiveData class. The MediatorLiveData class is the class I probably use most of all when dealing with LiveData (though I use map / switchMap / distinctUntilChanged when it makes sense).

To give you an example of when you should be creating your own MediatorLiveData class, look at this code:

By only mapping dbGame changes, I’m taking the current value of players (this.players.value) when the game is updated. So, I’m not updating the game when players are updated. To solve this I should instead use MediatorLiveData to merge players and games if any of them are updated. This would look something like this:

With this solution, I’m getting game updates whenever either players or dbGame is updated.

MediatorLiveData

MediatorLiveData can transform, filter and merge other LiveData instances. I tend to follow the same pattern whenever I create a MediatorLiveData, which looks something like this:

In this example, I’m observing two LiveData sources (a and b). I’m calling the update function when the mediator is created which will only emit a value if both sources are currently non-null. This pattern is very versatile, but let’s go through each step, one by one:

OPTION 1

What are the sources you want to monitor for changes before emitting anything from this LiveData. This can be just a single source (or more) but has no fixed upper limit. (I.e. lets you do a conditional map of a single LiveData or merge multiple LiveDatas)

OPTION 2

Call the inner update function here if you want to set an initial value when the MediatorLiveData is created. For simplicity's sake, I usually call my update function, but just setting the value /postValue of the MediatorLiveData also works. In some cases, I don’t want to emit an initial value as I’m expecting this to emit null if a or b isn’t yet set. I then just skip calling update or setting an initial value here.

OPTION 3

Since update will be called whenever either a or b emits an update we must expect a and b to be null. Sometimes you actually want to update your MediatorLiveData even though one or more sources are currently null, but this is a nice way of making sure the local variables aVal and bVal are non-null before emitting new values from the MediatorLiveData. You can even apply more validation/filtering here to reduce the emission of the final MediatorLiveData you’re creating.

OPTION 4

Since the MediatorLiveData is a LiveData instance we can either set the value (like in the example above) or call postValue (if for some reason you’re not on the main thread when emitting values). This is also where you decide how you want to transform the source data values. The example above just sums aVal and bVal, but you can of course apply any transformation you want here.

Conclusions

Use map, switchMap and distinctUntilChanged for all LiveData transformations. Avoid writing your own transformations unless necessary and try to combine operations to create more complex ones.

Use distinctUntilChanged to avoid emitting identical data, which causes unnecessary UI updates.

If you find yourself getting the current value of another LiveData using the .value property inside a map / switchMap or inside an observe-block, you should consider creating a MediatorLiveData to merge the sources properly instead.

Bonus

Now that you know how MediatorLiveData works you can go ahead and write your own amazing transformations. To get you started, here are some common extensions I’ve found or created for my own projects:

https://github.com/ptornhult/livedata-utils

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Responses (2)

What are your thoughts?