Karan Sharma
Karan Sharma

Reputation: 2599

How to convert 3 or more LiveData. into a single Flow in Android

The view model has 3 live data coming in from 3 different api calls. Now, I want to display all the data together as different cards in a list in the recycler view.

What would be the best way to do this ?

Currently I'm using MediatorLiveData, but now I need to have 4 different api calls, which would make 4 live data objects. Therefore, I'm trying to use kotlin flow for this problem. How can I combine the. 4 different Live Data coming in from different UseCases into a single Flow object and then collect it in the Fragment ?

Following is the method, where I'm trying to combine the 3 live data using combine method :

private val flow1 = pojo1UseCase.data.asFlow()
private val flow2 = pojo2UseCase.data.asFlow()
private val flow3 = pojo3UseCase.data.asFlow()


suspend fun combine() {
    flow1.combine(flow2) { f1, f2 ->
        listOf(f1.right(), f2.right())
    }.combine(flow3) { f1, f3 ->
        listOf(f1, f3.right())
    }.collect { list ->
        list.forEach {
            ba = it as? Pojo1
            oc = it as? Pojo2
            sd = it as? Pojo3
        }
    }
}

I'm calling collect here just for testing purpose, but i intend to call collect method in fragment. Can someone share a better way to do similar thing ?

Upvotes: 2

Views: 2014

Answers (1)

Tenfour04
Tenfour04

Reputation: 93629

Assuming this is simple and you are only expecting one value from each LiveData, combine should be fine, but you could use the variant that takes three arguments.

Also, since it's just another cold Flow, it can be in a simple property. The cold Flow would be backed by the LiveDatas, so the values retrieved aren't lost if something comes along after the results are received and collects the Flow. A suspend function is only necessary if you are collecting it.

val combined: Flow<List<Any>> = 
    combine(flow1, flow2, flow3) { a, b, c -> listOf(a, b, c) }

But I would recommend a class to hold your objects so you don't have to cast them when they come out.

data class MyResult(pojo1: Pojo1, pojo2: Pojo2, pojo3: Pojo3)

val combined: Flow<MyResult> = combine(flow1, flow2, flow3, ::MyResult)

Since there is nothing cancelling these Flows automatically, if you make a suspend function that collects them, I think you need to use first() or it will suspend indefinitely.

suspend fun combine() = with(combined.first()) {
    ba = pojo1
    oc = pojo2
    sd = pojo3
}

Or instead you could define your flow with take(1) to allow it to automatically complete on the first result.

val combined: Flow<MyResult> = combine(flow1, flow2, flow3, ::MyResult).take(1)

Edit, based on comment:

If you want to collect them indefinitely, you can start that internally, or you can create a function that you will call once to start collection. You should not use a suspend function, because whichever coroutine calls it would be stuck on the collect() call forever. It should just be a regular function that launches its own coroutine to collect it.

init { 
    combined.onEach {
        with(it) {
            ba = pojo1
            oc = pojo2
            sd = pojo3
        }
    }.launchIn(viewModelScope)
}

// or:

fun startCombining() {
    combined.onEach {
        with(it) {
            ba = pojo1
            oc = pojo2
            sd = pojo3
        }
    }.launchIn(viewModelScope)
}

Upvotes: 1

Related Questions