调用 Android Kotlin 协程中的 For Each 循环内同步 API 时,使用 Flow 出现错误。

huangapple go评论60阅读模式
英文:

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

问题

在 Android Kotlin Coroutines 中,在 For-each 循环中同步调用 API 并使用 Flow 进行操作。此外,需要等待所有 API 调用完成,而不阻塞主线程。

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)
    }
}

私有函数 getCreatedDate(Id: String?) 用于调用 API:

private fun getCreatedDate(Id: String?) = flow {
    try {
        val response = repoApi.getCount(Id)
        emit(response)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}.flowOn(Dispatchers.IO)

API 请求接口示例:

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

请注意,第一次迭代时数据正常发出,但第二次迭代后出现上述错误。

在 For 循环中使用 Flow 运行 API 调用,而不阻塞 UI 线程。提前感谢。

英文:

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&lt;Model&gt; = flow {
        emit(getFirstList())
    }.flatMapLatest { data -&gt;
        flow {
            val responseList = mutableListOf&lt;String&gt;()

            data.items?.forEach { datalist -&gt;

                val response = getCreatedDate(datalist.Id)


                response.collect{count-&gt;
                    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(
        &quot;Content-Type: application/json&quot;
    )
    @GET(&quot;getCount/{Id}&quot;)
    suspend fun getCount(@Path(&quot;Id&quot;)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.

答案1

得分: 1

不需要创建额外的 flow 并收集它,您可以将 getCreatedDate 转换为挂起函数,然后轻松在 flow 构建器的 for-loop 内部使用它。

@OptIn(ExperimentalCoroutinesApi::class)
fun getLatestData(): Flow<Model> = flow {
    emit(getFirstList())
}.flatMapLatest { data ->
    flow {
        val responseList = data.items?.mapNotNull { getCreatedDate(it.Id)?.check }.orEmpty()
        emit(Model(dataList = data, responseListCount = responseList))
    }
}

private suspend fun getCreatedDate(Id: String?): Response? =
    try {
        repoApi.getCount(Id)
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
英文:

No need to create an extra flow and collect it, you could convert getCreatedDate to suspend instead and easily use it inside for-loop of flow builder

@OptIn(ExperimentalCoroutinesApi::class)
fun getLatestData(): Flow&lt;Model&gt; = flow {
    emit(getFirstList())
}.flatMapLatest { data -&gt;
    flow {
        val responseList = data.items?.mapNotNull { getCreatedDate(it.Id)?.check }.orEmpty()
        emit(Model(dataList = data, responseListCount = responseList))
    }
}

private suspend fun getCreatedDate(Id: String?): Response? =  
    try { repoApi.getCount(Id) } 
    catch (e: Exception) { 
        e.printStackTrace() 
        null
    }

答案2

得分: 1

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 suspend 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.

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
    )
}
英文:

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&lt;Model&gt; = 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
    )
}

答案3

得分: 1

以下是代码部分的中文翻译:

当流被中止时,协程框架会抛出 AbortFlowException 来指示流收集应该停止。这个异常在流框架内部被捕获。只需添加挂起函数。

suspend 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 suspend 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
英文:

When a flow is aborted, the Coroutines framework throws the AbortFlowException to indicate that the flow collection should be stopped. This exception is caught internally by the flow framework. Just add suspend function.

suspend fun getLatesData(): Flow&lt;Model&gt; = flow {
    emit(getFirstList())
}.flatMapLatest { data -&gt;
    flow {
        val responseList = mutableListOf&lt;String&gt;()

        data.items?.forEach { datalist -&gt;

            val response = getCreatedDate(datalist.Id)


            response.collect{count-&gt;
                responseList.add(count.check)
            }
            val countData = Model(
                datalist = data,
                responseListCount = responseList
            )
        }
        emit(countData)
        
    }
}

 private suspend fun getCreatedDate(Id: String?)= flow {
        try {
            val response =  repoApi.getCount(Id)
            emit(response)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }.flowOn(Dispatchers.IO)

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

huangapple
  • 本文由 发表于 2023年5月25日 21:43:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76332977.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定