Reputation: 2904
I need to call API in for loop for each element, but it has to wait for each API call to return values, because if it will be asynchronous, order of returned elements could be mixed because each of those requests are asynchronous calls.
I want to wait inside for loop till value is returned. I used await()
for this but it is not working as expected. Results are returned after for loop.
Code:
parts.forEach {p->
if (p.listEmpty){
validItems.add(PartItem(TYPE_WITHOUT_LIST, p))
} else {
launch {
val listReturned = getItemsForPart(p)
validItems.add(PartItem(TYPE_WITH_LIST, p, listReturned))
}
}
}
private val scope = CoroutineScope(Dispatchers.IO)
private suspend fun getItemsForPart(p: InnerItemPart): List<InnerPart>{
val partResp = CompletableDeferred<List<InnerPart>>()
scope.launch{
val parent = api.getParentOfPart(p.partId)
var filteredParts = emptyList<InnerPart>()
if (parent != null){
val startIndex = parent.innerPart.indexOfFirst { it.partId == p.startId }
val endIndex = parent.innerPart.indexOfFirst { it.partId == p.endId }
filteredParts = parent.subList(startIndex, endIndex).toMutableList()
if (filteredParts.isNotEmpty()) filteredPart.removeAt(0)
}
partResp.complete(filteredParts)
}
return partResp.await()
}
Upvotes: 1
Views: 4199
Reputation: 93531
Maybe you have reasons for them that I don't see, but the scope just for this suspend function looks unnecessary since you aren't using it for encapsulating the task for cancellation. And the CompletableDeferred is an unnecessary extra layer of complexity. Use withContext
for the simplest way to do some background work and return it when done to a suspend function.
private suspend fun getItemsForPart(p: InnerItemPart): List<InnerPart> =
withContext(Dispatchers.IO) {
val parent = api.getParentOfPart(p.partId)
var filteredParts = emptyList<InnerPart>()
if (parent != null){
val startIndex = parent.innerPart.indexOfFirst { it.partId == p.startId }
val endIndex = parent.innerPart.indexOfFirst { it.partId == p.endId }
filteredParts = parent.subList(startIndex, endIndex).toMutableList()
if (filteredParts.isNotEmpty()) filteredPart.removeAt(0)
}
filteredParts
}
And to further simplify the code:
private suspend fun getItemsForPart(p: InnerItemPart): List<InnerPart> =
withContext(Dispatchers.IO) {
api.getParentOfPart(p.partId)?.let { parent ->
val startIndex = parent.innerPart.indexOfFirst { it.partId == p.startId }
val endIndex = parent.innerPart.indexOfFirst { it.partId == p.endId }
parent.subList(startIndex, endIndex).drop(1)
}.orEmpty()
}
Upvotes: 0
Reputation: 30595
You can launch a coroutine with Dispatchers.Main
in function where you go though each list item and use withContext(Dispatchers.IO)
in getItemsForPart()
function. withContext(Dispatchers.IO)
will change the context of the function to background thread:
fun applyForParts() = launch(Dispatchers.Main) {
parts.forEach { p->
if (p.listEmpty) {
validItems.add(PartItem(TYPE_WITHOUT_LIST, p))
} else {
val listReturned = getItemsForPart(p) // call blocks for each item
validItems.add(PartItem(TYPE_WITH_LIST, p, listReturned))
}
}
}
private suspend fun getItemsForPart(p: InnerItemPart): List<InnerPart> = withContext(Dispatchers.IO) {
val parent = api.getParentOfPart(p.partId)
var filteredParts = emptyList<InnerPart>()
if (parent != null) {
val startIndex = parent.innerPart.indexOfFirst { it.partId == p.startId }
val endIndex = parent.innerPart.indexOfFirst { it.partId == p.endId }
filteredParts = parent.subList(startIndex, endIndex).toMutableList()
if (filteredParts.isNotEmpty()) filteredPart.removeAt(0)
}
return@withContext filteredParts
}
Upvotes: 0
Reputation: 8096
When you use launch
inside forEach
you say to launch a coroutine and queue it to the context and let the next code run. So loop does not suspend (goes to for next iteration and so on).
If you look at the definition, forEach is an inline function, which means it is going to be inlined at the call-site upon compilation so you can use suspending function directly there.
parts.forEach {p->
if (p.listEmpty){
validItems.add(PartItem(TYPE_WITHOUT_LIST, p))
} else {
val listReturned = getItemsForPart(p) // suspends the current coroutine
validItems.add(PartItem(TYPE_WITH_LIST, p, listReturned))
}
}
You can use withContext for returning items it is lightweight and well optimized (unless you want to monitor child task, you don't need to create CoroutineScope).
private suspend fun getItemsForPart(p: InnerItemPart): List<InnerPart>{
return withContext(Dispatchers.IO) {
// your old code inside the launch, just put filteredParts in last line
filteredParts
}
}
Upvotes: 4