How to chain transformations in Android when using live data?

12,157

Solution 1

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.

Solution 2

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>.

Share:
12,157
Naveed
Author by

Naveed

Currently working as an Android Developer but interested in all technologies.

Updated on June 24, 2022

Comments

  • Naveed
    Naveed about 2 years

    Given the following setup:

    I have 2 repositories: Repository A and Repository B both of them return live data.

    I have a ViewModel that uses both of these repositories.

    I want to extract something from Repository A and depending on the result I want to grab something from Repository B and then transform the result before returning to UI.

    For this I have been looking at the LiveData Transformation classes. The examples show a single transformation of the result however I want something along the lines of chaining two transformations. How can I accomplish this?

    I have tried setting something up like this but get a type mismatch on the second transformation block:

      internal val launchStatus: LiveData<String> = Transformations
            .map(respositoryA.getData(), { data ->
                if (data.isValid){
                    "stringA"
                } else {
                    //This gives a type mismatch for the entire block
                    Transformations.map(repositoryB.getData(), {
                        result -> result.toString()
                    })
                }
            })
    

    (Also please let me know if there is an alternate/recommended approach for grabbing something for chaining these call i.e. grab something from A and then grab something from B depending on result of A and so on)

  • Naveed
    Naveed over 6 years
    Can you provide a working example? I have already tried the switchMap it needs to return live data however map satisfies my use case since I only need to transform the result to a string and not return as part of transformation.
  • imtoori
    imtoori over 6 years
    Ah, ok. But I don't understand why you return LiveData from repos if you don't use them directly as LiveData. I would return just a string and so the logic would be simpler in the ViewModel and you can just set the value in the ViewModel triggering the observer in the Activity/Fragment
  • Naveed
    Naveed over 6 years
    It is a specific use case. Sometimes the data from repos doesn't directly map to you UI thats where I use the ViewModel to transform it so it matches the UI. I'm following the MVVM architecture as described here: proandroiddev.com/…
  • amlwin
    amlwin almost 6 years
    How can achieve apiRequest that is base on observing 2 variable (eg. userId and apiKey). However Transformations.map can handle only 1 variable. Please give me some suggestion, to crossover this situation
  • netpork
    netpork almost 6 years
    documentation link is broken