1

I have function like this

fun getUsersList(userIds: List<String>): List<User> {

    val list = mutableListOf<User>()

    userIds.forEach { id ->
        getUserFromDatabase(id) { user ->
            list.add(user)
        }
    }

    return list
}

Ofcourse in this case method return empty list cos it call return list before async tasks in cycle is completed.
So how to make it wait until cycle will be completed before return?
I learned coroutine basics but still have no idea how to handle this, that's why i'm using callbacks to update viewModel.

UPD: To clarify my situation, I get (for example) List<Type1> from database and from this list get userId's and after that get User's from database. I need to build Map<Type1,User> and put it in viewModel. For that I use:

list<Type1>.forEach { type1 -> 
        getUserFromDatabase(type1.userId) { user ->
            map.put(type1, user)
        }
    }

Actually question was about returning result instead of callbacks and I got the answer. Further I'll handle it myself. Thank you all.

3
  • 1
    From the code it seems the best option would be to fetch all users with a single database query. Something like SELECT * FROM users WHERE id IN (<list of ids>). That's more performant and directly returns a List<User>. You won't have to bother with how to parallelize (if at all) the different calls and merge the results afterwards. You could write a new question if you need more help with the database part. Commented Mar 28 at 18:18
  • 1
    The question shows lack of understanding of DB design, the solution should be either SQL query with JOIN or if you have 2 separate DBs - it should be done with query mentioned by @tyg Commented Mar 28 at 18:52
  • @ViktorYakunin, tyg, You were right, I just didn't know how to create complex request to the database. Nevertheless, the question wasn't useless, I still had to find out how to return the value in the case of asynchronous tasks. Commented Mar 28 at 23:43

1 Answer 1

1

Build a List<Deferred<User>> and then call awaitAll() on it to get the resulting List<User>. All of that must be happening in a CoroutineScope - in my example it is created with coroutineScope, but you can use withContext or some other way.

suspend fun getUsersList(userIds: List<String>): List<User> = coroutineScope {
    buildList {
        userIds.forEach { id ->
            add(async {
                getUserFromDatabase(id)
            })
        }
    }.awaitAll()
}

Sign up to request clarification or add additional context in comments.

5 Comments

Since the function is called getUserFromDatabase I'm not sure if parallelizing the requests like this is the best approach. When each call results in a database query then you will effectively have concurrent access on the same file in the file system, most likely not very performant. Probably not noticeable when the list only contains a few entries, but still better to just call return userIds.map(::getUserFromDatabase) without any coroutines. Even better, make getUserFromDatabase retrieve a list of users directly so only a single database query is issued.
Also, you don't need a mutable list. Then you can simplify the coroutineScope block to this: userIds.map { async { getUserFromDatabase(it) } }.awaitAll(). Assuming getUserFromDatabase returns a User instead of executing a callback (OP's function does the latter, though).
@tyg Seems like you should post your comment as a solution.
@tyg what if users base too big? What will be more efficient: get a very few users or full list of (lets say) 1000 users?
It's always more efficient to let the database query the data that is actually needed, compared to distributing it over multiple queries (or retrieving all data and filtering it afterwards).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.