Kotlin Coroutines Tutorial for Android: Getting Started

In this Kotlin Coroutines tutorial, you’ll learn how to write asynchronous code just as naturally as your normal, synchronous code. By Amanjeet Singh.

Leave a rating/review
Download materials
Save for later
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Cleaning Up

Your last task is to clean up your coroutines, to avoid leaks. Override onDestroy and add the following code to it:

override fun onDestroy() {

This will cancel and clear up all the coroutines you launched with the coroutineScope.

And that’s all you need to implement your coroutine. Build and run your app and you’ll see the following:

Note: Once you cancel a Job, you cannot reuse it for coroutines. You have to create a new one. This is why it’s a good practice to either avoid adding Jobs to the CoroutineContext of your scope, or to recreate jobs according to your app’s lifecycle.

Writing Expressive Kotlin Coroutines

One thing we didn’t really pay attention to is the expressiveness of our Kotlin Coroutines. You did good context switching, to offload the main thread, but there’s some cognitive overhead you created. If you look at the code below:

private fun getOriginalBitmapAsync(tutorial: Tutorial): Deferred<Bitmap> =
    // 2
    coroutineScope.async(Dispatchers.IO) {
      // 3
      URL(tutorial.url).openStream().use {
        return@async BitmapFactory.decodeStream(it)

private fun loadSnowFilterAsync(originalBitmap: Bitmap): Deferred<Bitmap> =
    coroutineScope.async(Dispatchers.Default) {

It is returning a value asynchronously. But async() is better used if you have multiple requests. It’s really useful for parallelism, as you can run a few operations, without blocking or suspending, at the same time. Just create multiple async() blocks!

But here, it’s much better to go on the path of using withContext():

private suspend fun getOriginalBitmapAsync(tutorial: Tutorial): Bitmap =
    withContext(Dispatchers.IO) {
      URL(tutorial.url).openStream().use {
        return@withContext BitmapFactory.decodeStream(it)

private suspend fun loadSnowFilterAsync(originalBitmap: Bitmap): Bitmap =
    withContext(Dispatchers.Default) {

Instead of deferring the value, you mark the functions with suspend. This tells the caller it’s using a coroutine, and it might take some time to finish. But now, instead of having to await(), your code will change to this:

coroutineScope.launch(Dispatchers.Main) {
  val originalBitmap = getOriginalBitmapAsync(tutorial)
  val snowFilterBitmap = loadSnowFilterAsync(originalBitmap)

Which is much more intuitive and clean. There’s no additional overhead. Furthermore, the functions you call can determine the implementation details on how they will produce the value. This makes the code more decoupled if you decide to introduce more layers to your app.

Where to Go From Here?

Good job finishing the tutorial! In the end, you learned Kotlin Coroutines are not just another tool in a dusty shed we call asynchronous programming. The API is much more than that. It’s a new way to think about async programming overall. Which is really funny, because the concept dates back to the 50s and 60s. You saw how easy it was to switch between threads and return values asynchronously. You also saw how handling exceptions can be straightforward, and how cleaning up resources takes one function call.

You can download the final project by using the Download Materials button at the top or bottom of this tutorial.

If you want to learn more about coroutines, check out the Kotlin Documentation. You might also want to check out our Kotlin Coroutines by Tutorials book, which is out in stores. It brings an even more in-depth look at the Kotlin Coroutines and offers more tutorials.

I hope you enjoyed this tutorial on coroutines. Join us in the forums to discuss this tutorial and your findings as you work with them!