LiveData to the next level with MediatorLiveData and Transformations
An introduction to MediatorLiveData and Transformations to build reactive patterns with LiveData.

MediatorLiveData
From its explanation, it is a subclass of liveData which can ‘observe’ other LiveData and trigger onChanged() method when observed LiveData’s value is changed.
LiveData<String> source = ....; //
final MediatorLiveData<String> result = new MediatorLiveData<>();
result.addSource(source, new Observer<String>() {
@Override
public void onChanged(@Nullable String x) {
// do something here
}
});
lang-java
From the sample code above, when source’s value is changed, the onChanged method of Observer will be triggered. Transformations**
This class is way more like an utilisation of MediatorLiveData. It contains only 2 methods which are map() and switchMap()
@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
lang-java
These 2 methods doing the same thing which apply a function on the value stored in the LiveData object, and propagates the result downstream but the switchMap(), the function that passed in must return a LiveData object.
I’ve created a sample project here. You can take a look and stay with me to let me explain how the code works.
I use https://jsonplaceholder.typicode.com/ as a test api with /todos/1 endpoint. There is a created model name Todo to match with the response
import com.google.gson.annotations.SerializedName
// https://jsonplaceholder.typicode.com/todo/1
class Todo(
@SerializedName("userId") val userId: Int,
@SerializedName("id") val id: Int,
@SerializedName("title") val title: String,
@SerializedName("completed") val completed: Boolean
)
lang-kotlin
There are 2 more classes for handling responses from api.
import android.arch.lifecycle.LiveData
data class ResponseError(val code: Int? = null, val messages: String? = null)
data class ResponseResult<T>(val data: LiveData<T>, val error: LiveData<ResponseError>)
lang-kotlin
The ResponseResult will have 2 properties, both are a LiveData, one is for the response, the other one is for the error (ResponseError).
According to the Google suggestion, I made a repository module with will take a responsibility to fetch a data from network. In this module, I did some trick with ResponseResult:
class TodoRepository(private val api: TodoApi) {
fun getTodos(): ResponseResult<Todo> {
val data = MutableLiveData<Todo>()
val error = MutableLiveData<ResponseError>()
api.getTodos().enqueue(object : Callback<Todo> {
override fun onResponse(call: Call<Todo>, response: Response<Todo>) {
if (response.isSuccessful) {
data.value = response.body()
} else {
error.value = ResponseError(response.code(), response.errorBody()?.string())
}
}
override fun onFailure(call: Call<Todo>, t: Throwable) {
error.value = ResponseError()
}
})
return ResponseResult(data, error)
}
}
lang-kotlin
The function getTodos() will return ResponseResult<Todo>, its 2 properties, data’s value will be updated when response is finished, and error’s value will be updated when error happened.
By following the SOLID guideline, I keep my Usecase class so simple.
class GetTodoUsecases(private val repository: TodoRepository) {
operator fun invoke() = repository.getTodos()
}
lang-kotlin
Chaining LiveData**
Let’s have a look at my ViewModel and Fragment class:
class MainFragment: Fragment(){
...
...
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel._onActivityCreated.call()
}
...
...
...
}
class MainViewModel(private val getTodoUsecase: GetTodoUsecases) : ViewModel() {
val _onActivityCreated = SingleLiveEvent<Any>()
private val responseResult: LiveData<ResponseResult<Todo>> = Transformations.map(_onActivityCreated) {
getTodoUsecase()
}
val todoResult: LiveData<Todo> = Transformations.switchMap(responseResult) {
it.data
}
val errorResult: LiveData<ResponseError> = Transformations.switchMap(responseResult) {
it.error
}
}
lang-kotlin
I triggered a LiveData (SingleLiveEvent) at onActivityCreated(), and if you take a look at line 18, Transformation.map() is called which mean getTodoUsecase() will be called. After fetching is done, responseResult will be assigned. And if you take a look again at line 22 and 26, Transformation.switchMap() are called which means when the responseResult’s value is updated, todoResult and errorResult instances will be pointed to the same instances as _responseResult’s properties. _
Tips: OnChanged() method will not be called if MediatorLiveData is not observed at anywhere.
Reference:


