How to chain transformations in Android when using live data?

I used MediatorLiveData to solve this problem.

MediatorLiveData can observer other LiveData objects and react to them.

Instead of observing either of the repositories. I created myData (instance of MediatorLiveData) in my ViewModel class and have my view observe this object. Then I add Repository A as the initial source and observe that and only add Repository B if the result of A requires it. This allows me to keep the transformations associated with the live data of each of the repo and still process each result in the correct order. See below for implementation:

internal val myData: MediatorLiveData<String> = MediatorLiveData()

private val repoA: LiveData<String> = Transformations.map(
        respositoryA.getData(), { data ->
    if (data.isValid) "stringA" else ""

})

private val repoB: LiveData<String> = Transformations.map(
        repositoryB.getData(), { data -> "stringB" 
})

fun start() {
    myData.addSource(repoA, {
        if (it == "stringA") {
            myData.value = it
        } else {
            myData.addSource(repoB, {
                myData.value = it
            })
        }
    })
}

Note: The solution does not cover the case where repoB might be added multiple times but it should be simple enough to handle.


Your lambda sometimes returns the String "stringA", and sometimes returns the LiveData<String> given by:

Transformations.map(repositoryB.getData(), {
    result -> result.toString()
})

This means that your lambda doesn't make sense - it returns different things in different branches.

As others have mentioned, you could write your own MediatorLiveData instead of using the one given by Transformations. However, I think it's easier to do the following:

internal val launchStatus: LiveData<String> = Transformations
    .switchMap(respositoryA.getData(), { data ->
        if (data.isValid) {
            MutableLiveData().apply { setValue("stringA") }
        } else {
            Transformations.map(repositoryB.getData(), {
                result -> result.toString()
            })
        }
    })

All I've done is made the first code branch also return a LiveData<String>, so now your lambda makes sense - it's a (String) -> LiveData<String>. I had to make one other change: use switchMap instead of map. This is because map takes a lambda (X) -> Y, but switchMap takes a lambda (X) -> LiveData<Y>.