Search code examples
androidkotlinretrofit2kotlin-coroutineskotlin-flow

Call a API synchronously inside For Each loop in android Kotlin Coroutines with Flow getting Error


Call a API synchronously inside For-each loop in android Kotlin Coroutines using Flow.

Also i need to wait all the API calls need to complete without blocking the Main Thread.

fun getLatesData(): Flow<Model> = flow {
        emit(getFirstList())
    }.flatMapLatest { data ->
        flow {
            val responseList = mutableListOf<String>()

            data.items?.forEach { datalist ->

                val response = getCreatedDate(datalist.Id)


                response.collect{count->
                    responseList.add(count.check)
                }
                val countData = Model(
                    datalist = data,
                    responseListCount = responseList
                )
            }
            emit(countData)
            
        }
    }
  
     private fun getCreatedDate(Id: String?)= flow {
            try {
                val response =  repoApi.getCount(Id)
                emit(response)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }.flowOn(Dispatchers.IO)

 @Headers(
        "Content-Type: application/json"
    )
    @GET("getCount/{Id}")
    suspend fun getCount(@Path("Id")Id: String?): response

ERROR-kotlinx.coroutines.flow.internal.AbortFlowException: Flow was aborted, no more elements needed

For the first iteration Data is emitting properly, but second time onwards getting above mentioned error.

Run a API call without blocking the UI thread inside for loop with Flow. Thank in advance.


Solution

  • It doesn't make sense for getCreatedDate() to return a flow. A Flow is for a stream of values, not a single value. It can just be a suspend function.

    private fun getCreatedDate(id: String?) =
        try {
            repoApi.getCount(id)
        } catch (e: Exception) { 
            // Catching all exceptions is an antipattern. Be specific and rethrow unexpected ones.
            if (e !is IOException || e !is HttpException) throw e
            e.printStackTrace()
            null
        }
    

    Your other code can be simplified. No reason to create a flow of a single item and then flat map it. Just create the single flow directly. And use mapNotNull to more simply create your response list.

    fun getLatesData(): Flow<Model> = flow {
        val data = getFirstList()
        val responseList = data.items?.mapNotNull { getCreatedDate(it.id)?.check }.orEmpty()
        val countData = Model(
            datalist = data,
            responseListCount = responseList
        )
        emit(countData)
    }
    

    Edit: I just realized that we are ultimately ending up with a Flow of a single item here, which is the same nonsensical situation as I mentioned about getCreatedDate(). The whole thing should be a suspend function like this:

    suspend fun getLatesData(): Model  {
        val data = getFirstList()
        val responseList = data.items?.mapNotNull { getCreatedDate(it.id)?.check }.orEmpty()
        return Model(
            datalist = data,
            responseListCount = responseList
        )
    }
    

    If the API can handle doing the requests in parallel, this may be faster:

    suspend fun getLatesData(): Model  {
        val data = getFirstList()
        val responseList = coroutineScope {
                    data.items?.mapNotNull { async { getCreatedDate(it.id)?.check } }.orEmpty()
            }.awaitAll()
        return Model(
            datalist = data,
            responseListCount = responseList
        )
    }