Search code examples
androidmvvmkotlinandroid-databindingandroid-livedata

Android Logic Based On Multiple Live Data Values


I am using the Android data binding library to make reactive views with LiveData

I make a repo request for a list of jobs

var jobsRequest: LiveData<Resource<List<Job>>>
    = Transformations.switchMap(position) { repo.getJobsWithStatus(it) }

Then I have 3 more LiveData based on the above, like so

First, to check whether the request has completed

private val requestComplete: LiveData<Boolean>
    = Transformations.map(jobsRequest) { 
        it.status == Status.SUCCESS || it.status == Status.ERROR 
      }

Next, to transform to a list of jobs without the resource wrapper

var jobs: LiveData<List<Job>>
    = Transformations.map(jobsRequest) { it.data }

Lastly, to check if that job list is empty

val jobsEmpty: LiveData<Boolean>
    = Transformations.map(jobs) { (it ?: emptyList()).isEmpty() }

In the layout I want to show a loading spinner if the request has not completed and the jobs list is empty and need a variable in my view model to dictate this

I have tried the code below and, as expected, it does not work

val spinnerVisible: LiveData<Boolean>
    = Transformations.map(requestComplete) {
        !(requestComplete.value ?: false) && (jobsEmpty.value ?: true)
      }

What is the correct practice for having a LiveData variable based on the state of 2 others - I want to keep all logic in the view model, not in the activity or layout.


Solution

  • Is the jobsEmpty observer needed? Seems like you could reuse the jobs one for it.

    Anway, to your question: For this there is a MediatorLiveData. It does what you need: it can merge multiple (in your case: 2) LiveData objects and can determine another livedata value based on that.

    Some pseudo-code:

    MediatorLiveData showSpinner = new MediatorLiveData<Boolean>()
    showSpinner.addSource(jobsEmpty, { isEmpty ->
        if (isEmpty == true || requestComplete.value == true) {
           // We should show!
           showSpinner.value = true
        }
        // Remove observer again
        showSpinner.removeSource(jobsEmpty);
    })
    showSpinner.addSource(requestComplete, { isCompleted ->
        if (isCompleted == true && jobsEmpty == true) {
           // We should show!
           showSpinner.value = true
        }
        // Remove observer again
        showSpinner.removeSource(requestComplete);
    })
    return showSpinner
    

    Note that you need to return the mediatorlivedata as result, as this is the object you are interested in for your layout.

    Additionally, you can check the documentation on the MediatorLiveData, it has some more examples: https://developer.android.com/reference/android/arch/lifecycle/MediatorLiveData