How to transform Flow<List<Flow<T>>> to Flow<List<T>>
Asked Answered
J

3

9

I have a database query that returns the list of indexes of objects for a certain day, i.e.:

getIndexesOfDay(day: Day): Flow<List<String>>

Now, I need to listen for details for each index, for instance:

getDetailsByIndex(index: String): Flow<Details>

So the question is: how can I accomplish this using Flows?

Two important constraints:

  1. Changes on the indexes should propagate to the final flow;
  2. Changes on any Details object should also update the flow.

What I have already tried:

getIndexesOfDay(todayDay)
          .map { indexes ->
              indexes?.map {
                      mIndex->
                  getDetailsByIndex(mIndex)
              }
          }

But it returns a Flow<List<Flow<Details>>> instead of the desired Flow<List<Details>>

Appreciate any help!

Jessie answered 18/11, 2021 at 19:35 Comment(0)
C
13

I haven't tested this, but I believe it should be possible using flatMapLatest() and combine():

getIndexesOfDay(todayDay)
    .flatMapLatest { indexes ->
        combine(indexes.map { getDetailsByIndex(it) }) { it.asList() }
    }

For each new list of indexes, it invokes getDetailsByIndex() multiple times to acquire a list of flows and then combines these flows into a single flow of details lists.

The drawback is that whenever indexes change, it throws away all Flow<Details> objects, even if an index existed in both old and new list of indexes. It would be probably better to create some kind of a cache of detail flows per each index.

Cinderellacindi answered 18/11, 2021 at 20:12 Comment(2)
I think asList() is acceptable here. The docs for combine have no warning about leaking out the Array, and the source code shows they take care not to reuse Array instances.Ahoufe
Thanks, updated @AhoufeCinderellacindi
O
0

Hi I loved the @broot solution.

But I have an issue with the Data base empty case. It is loading forever. :(

After reading https://medium.com/digigeek/from-zero-to-hero-series-kotlin-sequences-channels-flows-part-3-flows-5cdb391295b8

I decided to write some code:

override suspend fun getProductModels() : Flow<List<ProductModel>> {
         return withContext(Dispatchers.IO) {
             return@withContext productDAO.getProductEntities().flatMapLatest {
                     productEntities: List<ProductWithElements> ->
                 
                 if(productEntities.isEmpty()) {
                     val productModelListFlow : Flow<List<ProductModel>> =  flow {
                             emit(emptyList())
                     }
                     return@flatMapLatest productModelListFlow 
                 }
                 val productModelFlowList : List<Flow<ProductModel>> = productEntities.map { 
                         productEntity: ProductWithElements ->
                     photoDAO.getPhotoEntities(productEntity.product.productId).map {
                         productEntity.toModel(it)
                     }
                 }
                 
                 //combine Returns a Flow
                 // whose values are generated with transform function
                 // by combining the most recently emitted values by each flow.
                 val productModelListFlow : Flow<List<ProductModel>> = combine(productModelFlowList){
                         arrayOfProductModels: Array<ProductModel> ->
                     arrayOfProductModels.asList()
                 }
                 return@flatMapLatest productModelListFlow
             }
         }
     }

I change the flatMapLastest with FlatmapConcat to wait the completition of the inner flow.

Edit: I change the FlatmapConcat with flatMapLastest to auto update the screen of my composables.

Thanks.

Happy Hacking !

Osi answered 27/7, 2023 at 19:56 Comment(0)
M
-1

You could try something like this:

getIndexesOfDay(todayDay)
      .map { indexes ->
          indexes?.map { mIndex->
              getDetailsByIndex(mIndex)
          }.combineList()
      }

fun<T> List<Flow<T>>.combineList(): Flow<List<T>> {
    return fold(flowOf(emptyList())) { acc, flow ->
        acc.combineTransform(flow) { list, item ->
            emit(list + item)
        }
    }
}

May be it's not a good idea for a big lists but works in simple cases.

Museum answered 13/6, 2024 at 18:17 Comment(1)
Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?Prodigal

© 2022 - 2025 — McMap. All rights reserved.