Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating a MediatorLiveData to SharedFlow

I have a MediatorLiveData that uses three LiveData sources. When any of them emits a new value and I have at least one of each, I use the three values to produce the output for the UI.

Two of the sources are user settings for how to sort and filter a list, and the third is the list data, pulled from a Room database Flow.

It looks something like this:

val thingsLiveData: LiveData<List<Thing>> = object: MediatorLiveData<List<Thing>>() {
    var isSettingA: Boolean = true
    var settingB: MySortingEnum = MySortingEnum.Alphabetical
    var data: List<Thing>? = null
    init {
        addSource(myRepo.thingsFlow.asLiveData()) {
            data = it
            dataToValue()
        }
        addSource(settingALiveData) {
            isSettingA= it
            dataToValue()
        }
        addSource(settingBLiveData) {
            settingB= it
            dataToValue()
        }
    }
    private fun dataToValue() {
        data?.let { data ->
            viewModelScope.launch {
                val uiList = withContext(Dispatchers.Default) {
                    produceUiList(data, isSettingA, settingB)
                }
                value = listItems
            }
        }
    }
}

I'm looking for a clean way to convert this to a SharedFlow, preferably without any @ExperimentalCoroutinesApi. The only SharedFlow builder function I've come across is callbackFlow, which isn't applicable. Are you intended to use flow { ... }.asSharedFlow(...) in most cases, and if so, what would that look like here?

The two settings LiveData I also plan to migrate to flows.

like image 564
Tenfour04 Avatar asked Dec 22 '20 00:12

Tenfour04


1 Answers

The source Flows can be combined using combine(), which creates a cold Flow that, when collected, will start collecting from its source Flows, which may be hot or cold.

I was originally thinking that I must be missing something and that there should be some way to directly combine hot Flows into a combined hot Flow. But I realized it makes sense that the operators should only return cold Flows and leave it up to you to convert it back to a hot Flow if that's what you need.

In many cases, such as mine, it's perfectly fine to leave it cold. I only collect this Flow from one place in my UI, so it doesn't matter that it only starts combining the sources when it's collected. The source hot Flows don't care whether something is currently collecting them or not...they just keep emitting regardless.

If I collected this Flow from multiple places or multiple times, then it might make sense to use shareIn on the combined Flow to make it hot, which would avoid redundant work of combining the sources. The potential downside would be that it would combine those sources even when nothing is collecting, which would be wasted work.

val thingsFlow: Flow<List<Thing>> = combine(
    myRepo.thingsFlow,
    settingALiveData.asFlow(),
    settingBLiveData.asFlow()
) { data, isSettingA, settingB -> produceUiList(data, isSettingA, settingB) }

// where produceUiList is now a suspend function that wraps 
// blocking code using withContext
like image 181
Tenfour04 Avatar answered Oct 26 '22 13:10

Tenfour04