How to get LiveData to switch between two other LiveData

Viewed 561

I have the following scenario. Podcasts can come from internet or local(db) both are LiveData

// Live
private val _live = MutableLiveData<List<Podcast>>()
val live: LiveData<List<Podcast>> = _live

// Local
val local: LiveData<List<Podcast>> = dao.observePodcasts()

// Combined
val podcasts: LiveData<List<Podcast>> = ...

My question is:- How can i use only one LiveData podcasts such that on demand I can get data from live or local

fun search(query: String) {
    // podcasts <- from live
}

fun subcribed() {
    // podcasts <- from local
}
4 Answers

You can use MediatorLiveData in this case. What you need to do with MediatorLiveData is need the LiveData sources to be able to listen for changes to the LiveData source.

Try the following:

YourViewModel.kt

private val _podcasts = MediatorLiveData<List<Podcast>>().apply {
    addSource(_live) { dataApi ->
        // Or you can do something when `_live` has a change in value.
        if(local.value == null) {
            this.value = dataApi  
        }
    }

    addSource(local) { dataLocal ->
        // Or you can do something when `local` has a change in value.
        if(_live.value == null) {
            this.value = dataLocal
        }
    }
}
val podcasts: LiveData<List<Podcast>> = _podcasts

MediatorLiveData

I've personally used MediatorLiveData in projects to achieve the same function you're describing.

As quoted directly from the docs since they are pretty straight forward...

Consider the following scenario: we have 2 instances of LiveData, let's name them liveData1 and liveData2, and we want to merge their emissions in one object: liveDataMerger. Then, liveData1 and liveData2 will become sources for the MediatorLiveData liveDataMerger and every time onChanged callback is called for either of them, we set a new value in liveDataMerger.

LiveData liveData1 = ...;
LiveData liveData2 = ...;

MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));

As already suggested, this can be accomplished with MediatorLiveData. Another option would be using Flows instead of combining LiveData.

    val podcasts = combine(local, live) { local, live ->
        // Add your implementation of how you would like to combine them
        live ?: local
    }.asLiveData(viewModelScope.coroutineContext)

If you're using Room, you can simply change the return type to Flow to get a Flow result. And for the MutableLiveData you can replace it with MutableStateFlow.

Using MediatorLiveData didn't suit my needs as I expected because I wanted to be able to switch between local and live whenever I want!

So I did the implementation as follows

enum class Source {
    LIVE, LOCAL
}

private val _live = MutableLiveData<List<Podcast>>()
private val _local = dao.observePodcasts()
private val source = MutableLiveData<Source>(Source.LOCAL)

// Universal
val podcasts: LiveData<List<Podcasts>> = source.switchMap {
    liveData {
        when (it) {
            Source.LIVE -> emitSource(_live)
            else -> emitSource(_local)
        }
    }
}

emitSource() removes the previously-added source.

Then I implemented the following two methods

fun goLocal() {
    source.postValue(Source.LOCAL)
}

fun goLive() {
    source.postValue(Source.LIVE)
}

I then call respected function whenever to observer from live or local storage

One of the usecase

searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
    override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
        viewModel.goLive()
        return true
    }

    override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
         viewModel.goLocal()
         return true
    }
})
Related