0
votes

I have a question related to Kotlin flow.

Let's say there are two network APIs:

  • one is to get a list of IDs; and
  • the other one is given an ID, get an user

I what to

  1. Use the 1st API to get the IDs
  2. Use the 2nd API to get all the users
  3. Return the list of the users to UI for display

To me, this looks like a perfect use case for Kotlin flows. I would probably do it like this:

// NetworkApi.kt

interface NetworkApi {

  @GET("ids")
  suspend fun getIds(): List<Int>

  @GET("users/{id}")
  suspend fun getUser(id: Int): User
}

And in my ViewModel:

class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
  
  val usersLiveData = flow {
    emit(networkApi.getIds())
  }.flatMapConcat { // it: List<Int>
    val flowList = mutableListOf<Flow<User>>()
    val userList = mutableListOf<User>()
    
    for (id in it) {
      flowList.add(flow{ emit(networkApi.getUser(id)) })
    }
    flowList.merge().toList(userList)
    userList
  }.asLiveData()
}

Note that I used merge to send out the user requests in parallel (rather than one after another), to shorten the latency.

However, according to the documentation of merge, it will NOT be "preserving an order of elements".

I would like to have the benefits of parallelization, as well as preserving the order of elements. Is there a way to do that, please?

1

1 Answers

1
votes

I don't see how this is a perfect fit for Flows. You request something once, and you get a result once. It's not a continuing flow of changing data. (If you automatically retrieved a new list every time the list from the network changed, then Flows would make sense.)

If you think about it in those terms, the code is quite simple and you only need to expose a suspend function.

class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
  
    suspend fun getUsers(): List<User> = networkApi.getIds()
        .map { async { networkApi.getUser(it) } }
        .awaitAll()

}

And if you really want to expose this as LiveData (that will only ever publish one value):

val usersLiveData: LiveData<List<User>> = MutableLiveData<List<User>>().apply {
    viewModelScope.launch {
        value = getUsers()
    }
}

Or to expose it as a SharedFlow with only one published value:

val users: SharedFlow<List<User>> = MutableSharedFlow<List<User>>(replay = 1).apply {
    viewModelScope.launch { 
        emit(getUsers())
    }
}