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:
- The
LiveData
to be added as a source, and - A lambda, method reference, or other
Observer
to say what should happen when events are emitted by that source
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.