Handling Changes to LiveData

The approach in the preceding section works great, so long as your LiveData itself is unchanging. It might be emitting new results to the data binding observer, but the LiveData object is the same for the entire use of that layout and activity.

But what happens when you need to use a different LiveData object?

For example, perhaps you are showing search results for data stored in Room. So, you query your DAO and get a LiveData with those search results and show them via data binding. But the search results UI has its own search field, for making a new search. When the user searches, you want to re-query the DAO and show the new results in the same fragment or activity. But now you have a new LiveData, and you need to get that applied to the data binding.

One simple answer would be to put the LiveData directly in a data binding <variable>. This works, so long as you have a custom LiveData subclass, as data binding <variable> declarations do not support generics. However, you need to be in position to call your variable setter on the binding object each time you get a new LiveData. That may or may not be practical, depending on your architecture and where the LiveData is coming from.

What would be nice is if we could have data binding use a single LiveData object, as in our earlier examples, but have that LiveData object emit objects that come from other LiveData sources. This is a common technique in RxJava when you have the same sort of situation: some subscribers to an Observable happen to need a stable Observable for simpler subscription management, but you get new Observable objects from some API (e.g., new Retrofit calls). So, you use a Subject, such as a BehaviorSubject as the stable Observable, and you have the on-the-fly Observables feed their output into the Subject as input.

The equivalent approach with LiveData is MediatorLiveData. MediatorLiveData is designed to take output from another LiveData as input, emitting those same objects. Those LiveData inputs can come and go, and observers can just observe the MediatorLiveData.

The Sensor/LiveMediator sample project uses this approach.

The activity, layout, and SensorLiveData are the same as in the previous example. What differs is the SensorViewModel, and what sort of object is used for the sensorLiveData field. Previously, that was the SensorLiveData itself. Now, it is a MediatorLiveData:

package com.commonsware.android.livedata;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.MediatorLiveData;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.support.annotation.NonNull;

public class SensorViewModel extends AndroidViewModel {
  public final MediatorLiveData<SensorLiveData.Event> sensorLiveData=
    new MediatorLiveData<>();

  public SensorViewModel(
    @NonNull Application app) {
    super(app);

    SensorLiveData opLiveData=
      new SensorLiveData(app, Sensor.TYPE_LIGHT, SensorManager.SENSOR_DELAY_UI);

    sensorLiveData.addSource(opLiveData, sensorLiveData::setValue);
  }
}

When we create the SensorViewModel, we still create an instance of SensorLiveData. But then we add it as a source to the MediatorLiveData via addSource(). addSource() takes two parameters:

In this case, we use a method reference to feed the output from the SensorLiveData directly into the MediatorLiveData. In other circumstances, you might have a lambda that performs some sort of data conversion.

The behavior of the app overall does not change, because the events emitted by the SensorLiveData flow through the MediatorLiveData to our UI via data binding and the binding expression.

In this case, this adds no value, and we would be better served by using the previous example. However, suppose that our UI allowed the user to choose a different sensor. Now, we could tell our SensorViewModel to connect to a different SensorLiveData tied to the newly-selected sensor. We would want to disconnect the old SensorLiveData, which would require a call to removeSource() on the MediatorLiveaData.

We will see this technique used again in an upcoming chapter. There, we schedule multiple WorkManager pieces of work, and we want to keep track of the status of each of them, but using a single LiveData for our UI, instead of individual LiveData objects per piece of work.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.