AndroidDev
AndroidDev

Reputation: 21237

Iterating Coroutines and awaiting results

I have a situation where I need to dispatch an indeterminate number of network calls known only at runtime. Each call returns a list. As each is returned, I need to combine these lists in to a single merged list. I am using coroutines to do this.

The problem I am having relates to the fact that I do not know how many network calls the app will need to make. To address this, I am using a loop to iterate over the list of calls at runtime:

private suspend fun fetchData(params: List<Interval>): List<Item> {

    val smallLists = mutableListOf<Deferred<List<Item>>>()
    val merged = mutableListOf<List<Item>>()

    for (index in 0 until params.size) {
        val param = params[index]
        // loop stop iterating after this call is dispatched
        smallLists[index] = CoroutineScope(Dispatchers.IO).async {
            fetchList(param)
        }
    }

    for (index in 0 until smallLists.size) {
        merged[index] = smallLists[index].await()
    }

    return merged.flatMap { it.toList() }
}

private fun fetchList(param: Interval) : List<Item> {
    return dataSource.fetchData(param)
}

What happens in this code is that it enters the first loop. The params list is correct. It dispatches the first query, and this query returns (I can see this via a Charles proxy).

But this is where everything just dies. The app does nothing with the network response and the loop terminates (i.e. there is no second iteration of the loop).

I know that everything else is intact because I have an alternate version that does not include looping. It just does two queries, awaits their results, and returns the combined list. It works fine, except that it won't handle a dynamic runtime situation:

private suspend fun fetchData(params: List<Interval>): List<Item> {        
    val list1 = CoroutineScope(Dispatchers.IO).async {
        fetchList(params[0])
    }

    val list2 = CoroutineScope(Dispatchers.IO).async {
        fetchList(params[1])
    }

    return list1.await() + list2.await()
}

Probably a simple solution here, but I don't see it. Any help is appreciated.

Upvotes: 1

Views: 1008

Answers (1)

Francesc
Francesc

Reputation: 29260

This is not correct:

smallLists[index] = CoroutineScope(Dispatchers.IO).async {
        fetchList(param)
    }

Your smallLists is empty, so you can't access index index. Change it like this

smallLists.add(CoroutineScope(Dispatchers.IO).async {
        fetchList(param)
    }
)

Note that you can call awaitAll() on your list of asyncs as well, to simplify your code:

private suspend fun fetchData(params: List<Interval>): List<Item> {

    val smallLists = mutableListOf<Deferred<List<Item>>>()

    for (index in 0 until params.size) {
        val param = params[index]
        // loop stop iterating after this call is dispatched
        smallLists.add(CoroutineScope(Dispatchers.IO).async {
            fetchList(param)
        }
    })

    val merged = smallLists.awaitAll()
    return merged.flatMap { it.toList() }
}

Upvotes: 2

Related Questions