Intro to Kotlin Coroutines


Kotlin Coroutines for Beginners: Learn with Examples

If you’re new to Kotlin coroutines, you might feel overwhelmed by the concept of asynchronous programming. But don’t worry! Coroutines are here to make your life easier. In this blog post, we’ll start from the very basics and build up your understanding with simple examples.


What Are Coroutines?

Coroutines are like lightweight threads. They allow you to perform tasks asynchronously (in the background) without blocking the main thread. This is especially useful for tasks like:

  • Fetching data from the internet
  • Reading/writing to a database
  • Performing heavy computations

The best part? Coroutines make your code look sequential and easy to read, even though it’s running asynchronously.


Key Concepts

Before we dive into examples, let’s understand a few key terms:

  1. Coroutine: A piece of code that runs asynchronously.
  2. Suspend Function: A function that can pause its execution and resume later without blocking the thread.
  3. Scope: A way to manage the lifecycle of coroutines (e.g., GlobalScope, runBlocking).
  4. Dispatcher: Determines which thread the coroutine runs on (e.g., Dispatchers.Main, Dispatchers.IO).

Example 1: Hello World with Coroutines

Let’s start with the simplest example: printing “Hello, World!” using a coroutine.

import kotlinx.coroutines.*

fun main() {
    println("Start")

    // Launch a coroutine
    GlobalScope.launch {
        delay(1000L) // Pause for 1 second
        println("Hello, World!")
    }

    println("End")
    Thread.sleep(2000L) // Keep the main thread alive
}

Output:

Start
End
Hello, World!

Explanation:

  1. GlobalScope.launch starts a new coroutine.
  2. delay(1000L) pauses the coroutine for 1 second without blocking the main thread.
  3. Thread.sleep(2000L) keeps the main thread alive so the coroutine can finish.

Example 2: Using runBlocking

In the previous example, we used Thread.sleep to keep the main thread alive. But this isn’t ideal. Instead, we can use runBlocking, which creates a coroutine scope and blocks the main thread until the coroutine finishes.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    launch {
        delay(1000L)
        println("Hello from coroutine!")
    }

    println("End")
}

Output:

Start
End
Hello from coroutine!

Explanation:

  1. runBlocking creates a coroutine scope and blocks the main thread.
  2. launch starts a new coroutine inside the runBlocking scope.
  3. The coroutine pauses for 1 second and then prints “Hello from coroutine!”.

Example 3: Suspending Functions

A suspending function is a function that can pause its execution and resume later. Let’s create a suspending function to simulate a network request.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    launch {
        val result = fetchData()
        println(result)
    }

    println("End")
}

// Suspending function
suspend fun fetchData(): String {
    delay(2000L) // Simulate a network request
    return "Data fetched!"
}

Output:

Start
End
Data fetched!

Explanation:

  1. fetchData() is a suspending function that simulates a network request by pausing for 2 seconds.
  2. The launch coroutine calls fetchData() and prints the result.

Example 4: Running Multiple Coroutines

You can run multiple coroutines concurrently. Let’s simulate two tasks running at the same time.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    launch {
        delay(1000L)
        println("Task 1 done!")
    }

    launch {
        delay(1500L)
        println("Task 2 done!")
    }

    println("End")
}

Output:

Start
End
Task 1 done!
Task 2 done!

Explanation:

  1. Two coroutines are launched concurrently.
  2. The first coroutine finishes after 1 second, and the second finishes after 1.5 seconds.

Example 5: Using async and await

If you need to perform multiple tasks concurrently and wait for their results, use async and await.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    val result1 = async {
        delay(1000L)
        "Result 1"
    }

    val result2 = async {
        delay(1500L)
        "Result 2"
    }

    println("End")
    println("${result1.await()} and ${result2.await()}")
}

Output:

Start
End
Result 1 and Result 2

Explanation:

  1. async starts a coroutine and returns a Deferred object, which is a promise of a future result.
  2. await() waits for the result of the Deferred object.
  3. Both coroutines run concurrently, and their results are printed at the end.

Example 6: Using Dispatchers

Coroutines can run on different threads using dispatchers. For example, you can use Dispatchers.IO for network requests or Dispatchers.Main for UI updates.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start on thread: ${Thread.currentThread().name}")

    launch(Dispatchers.Default) {
        println("Running on thread: ${Thread.currentThread().name}")
        delay(1000L)
        println("Task done!")
    }

    println("End on thread: ${Thread.currentThread().name}")
}

Output:

Start on thread: main
End on thread: main
Running on thread: DefaultDispatcher-worker-1
Task done!

Explanation:

  1. Dispatchers.Default runs the coroutine on a background thread.
  2. The main thread is not blocked, and the coroutine runs concurrently.

Example 7: Handling Errors

Coroutines provide a way to handle errors using try-catch.

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    launch {
        try {
            delay(1000L)
            throw RuntimeException("Something went wrong!")
        } catch (e: Exception) {
            println("Error: ${e.message}")
        }
    }

    println("End")
}

Output:

Start
End
Error: Something went wrong!

Explanation:

  1. The coroutine throws an exception after 1 second.
  2. The exception is caught and handled in the catch block.

Summary

Here’s what we’ve learned:

  1. Coroutines are lightweight and great for asynchronous tasks.
  2. Use launch to start a coroutine and delay to pause it.
  3. Use async and await for concurrent tasks.
  4. Use Dispatchers to control which thread the coroutine runs on.
  5. Handle errors with try-catch.

With these examples, you should have a solid foundation to start using Kotlin coroutines in your projects. Practice these concepts, and soon you’ll be writing efficient and clean asynchronous code!

Happy coding! 🚀