Reputation: 258
I'm slowly learning kotlin-coroutines
and want I wanted to start with was simple case where I iterate through list and each time call an API call, wait for response, do with it and then repeat the process every iteration.
viewModelScope.launch {
list.forEach { item ->
Log.d("TESTING", "forEach iteration before calling test(): ${item.id}")
test()
Log.d("TESTING", "forEach iteration after calling test(): ${item.id}")
}
}
suspend fun test(item.id: String) = coroutineScope {
Log.d("TESTING", "suspend test before receiving response: ${item.id}")
val apiRepsonse = async {
repository.doingApiCall() {
Log.d("TESTING", "response: ${item.id}")
}
}.await()
Log.d("TESTING", "suspend test after receiving response: ${item.id}")
}
So, if we imagine, that list
contains: { 10, 15, 25, 35 }
(id's)
I'd want to receive in logcat
following:
Log.d("TESTING", "forEach iteration before calling test(): 10")
Log.d("TESTING", "suspend test before receiving response: 10")
Log.d("TESTING", "response: 10")
Log.d("TESTING", "suspend test after receiving response: 10")
Log.d("TESTING", "forEach iteration after calling test(): 10")
___________________
Log.d("TESTING", "forEach iteration before calling test(): 15")
Log.d("TESTING", "suspend test before receiving response: 15")
Log.d("TESTING", "response: 15")
Log.d("TESTING", "suspend test after receiving response: 15")
Log.d("TESTING", "forEach iteration after calling test(): 15")
...
But, right now I'm getting something like this:
Log.d("TESTING", "forEach iteration before calling test(): 10")
Log.d("TESTING", "suspend test before receiving response: 10")
Log.d("TESTING", "suspend test after receiving response: 10")
Log.d("TESTING", "forEach iteration after calling test(): 10")
Log.d("TESTING", "forEach iteration before calling test(): 15")
Log.d("TESTING", "suspend test before receiving response: 15")
Log.d("TESTING", "suspend test after receiving response: 15")
Log.d("TESTING", "forEach iteration after calling test(): 15")
Log.d("TESTING", "response: 10")
Log.d("TESTING", "response: 15")
...
Basically, for-each loop moves and does not wait for the API async response. I thought .await()
is intended for this scenerio, but I might be missing something here as it clearly does not work as I imagined it.
Upvotes: 1
Views: 1471
Reputation: 93561
Most likely repository.doingApiCall()
is not a proper suspend function and is firing off some asynchronous action without suspending the coroutine. It looks like it takes a lambda as a callback, another clue that it is not a suspending function.
And if it were a suspending function, you wouldn't need to dispatch it with async
.
As an aside, using async
and immediately calling await
on it is a more convoluted and less optimized way of using withContext(Dispatchers.Default)
and it also swallows exceptions silently.
Upvotes: 2
Reputation: 10038
Did not test, this should work:
// same code here
viewModelScope.launch {
list.forEach { item ->
Log.d("TESTING", "forEach iteration before calling test(): ${item.id}")
test()
Log.d("TESTING", "forEach iteration after calling test(): ${item.id}")
}
}
suspend fun test(item.id: String) = withContext(Dispatchers.IO) {
Log.d("TESTING", "suspend test before receiving response: ${item.id}")
repository.doingApiCall() {
Log.d("TESTING", "response: ${item.id}")
}
Log.d("TESTING", "suspend test after receiving response: ${item.id}")
}
Two things changed:
coroutineScope
, use withContext()
so it suspends the callerwithContext()
, you don't need to use async.await
Upvotes: 0
Reputation: 76458
Change
val apiRepsonse = async {
repository.doingApiCall() {
Log.d("TESTING", "response: ${item.id}")
}
}.await()
to
val apiRepsonse = async {
Log.d("TESTING", "Start work in another 'thread'.")
repository.doingApiCall() {
Log.d("TESTING", "Finish work in another 'thread'.")
}
}.await()
Log.d("TESTING", "response: ${apiRepsonse.id}")
That way the value that your await is delivering is actually used.
I think it'll clear up your LogCat confusion.
I was trying to work through your problem myself, I have written it up as a test that can be run to be understood:
class TestFoo {
@Test
fun foo() {
val job = GlobalScope.launch {
listOf("10", "15").forEach { item ->
println("TESTING forEach iteration before calling test(): ${item}")
test(item)
println("TESTING forEach iteration after calling test(): ${item}")
}
}
while(job.isActive) {
// wait
}
}
private suspend fun test(item: String) = coroutineScope {
println("TESTING suspend test before receiving response: ${item}")
val apiRepsonse = async {
delay(1000)
println("TESTING response: ${item}")
"Hello"
}.await()
println("TESTING suspend test after receiving response: ${item}")
}
}
And here is the "LogCat":
Are you getting different logs because of some threading that is happening inside of repository.doingApiCall()
?
Upvotes: 2