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)
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.
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().
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.
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.
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>
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With