Awaiting Multiple Coroutines the Clean Way

Matt Robertson
ProAndroidDev
Published in
2 min readFeb 14, 2022

--

When we launch a coroutine in Kotlin using launch we are returned the resulting Job. We can wait for the coroutine to finish by calling join() on the Job. For example, suppose we have a suspend function to download some files.

We can launch this coroutine and capture the resulting job, which we can later use to join — to wait for the operation to complete.

One use of join is to fire off multiple concurrent operations and wait for all of them to complete (in whatever order) before continuing. For example,

To clean it up a bit, Kotlin’s coroutines package provides a handy extension function Collection<Job>.joinAll(). We can refactor the above code as follows:

This performs the same operations but makes the code much more readable. Note too that when we call launch we are actually launching the coroutines, not simply defining coroutines for later use. To use the terminology that Kotlin uses for flows, these coroutines are “hot” not “cold.” Each coroutine is launched immediately and the resulting Job is added to the list downloadJobs.

We can make the code even more readable by adding our own utility function.

This produces the following final result:

The resulting code is now fairly complex but extremely readable.

As a disclaimer, technically await refers to async operations and join refers to launch operations. I used “await” here because I find “await” to be much more readable than “join,” which always makes me think of concatenations or unions at first glance. If your codebase makes extensive use of both launch and async operations, you may be better off naming the utility function joinAll.

Alternatively we can pass the suspend blocks themselves directly as follows:

In which case the call will look like this:

The final product might look something like this:

This can be useful if, for example, we want to display a progress spinner until all downloads have finished, but we want the downloads to run in parallel rather than sequentially.

And there we have it. Extremely readable structured concurrency with Kotlin. Happy coding!

Follow for more on best practices in Kotlin and Android development.

--

--

Android Engineer @ Crossway. Writing on clean arch, clean code, Kotlin, coroutines, & Compose.