Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to chain transformations in Android when using live data?

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)

like image 806
Naveed Avatar asked Nov 12 '17 20:11

Naveed


People also ask

When transformations map method is executed on live data What will it return?

map. Applies the given function on the main thread to each value emitted by source LiveData and returns LiveData, which emits resulting values. The given function func will be executed on the main thread.

How do you transform live data?

If you want to transform value that emitted through LiveData you can use Transformations. map(). If you want to transform value that emitted through LiveData into another LiveData you can use Transformations. switchMap().

Is live data deprecated in Android?

Starts observing this LiveData and represents its values via State . Starts observing this LiveData and represents its values via State . This function is deprecated. This extension method is not required when using Kotlin 1.4.

How can use mutable live data in Android?

The MutableLiveData class exposes the setValue(T) and postValue(T) methods publicly and you must use these if you need to edit the value stored in a LiveData object. Usually MutableLiveData is used in the ViewModel and then the ViewModel only exposes immutable LiveData objects to the observers.


2 Answers

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

like image 61
Chrispher Avatar answered Sep 22 '22 03:09

Chrispher


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.

like image 33
Naveed Avatar answered Sep 18 '22 03:09

Naveed