CodeBlanks Logo Icon CodeBlanks

Coroutines

Write async processing with coroutines and Flow.

Suspend calls with coroutines

The fourth lesson makes UserRepo and Api methods suspend. This mimics Ktor's suspend-based API. main now opens with runBlocking.

Example: suspend fun save(u: User). suspend fun find(id: Int): User?. main = runBlocking { repo.save(...); api.route(...) }. suspend functions are called inside a coroutine. This pattern is suitable for concurrent I/O; it does not block threads.

runBlocking and coroutine scope

runBlocking opens a coroutine scope and blocks. suspend functions are called like normal functions inside this scope.

Example: fun main() = runBlocking { repo.save(User(1, "Alex")); println(api.route("/users/1")) }. runBlocking keeps main from finishing until the coroutine completes. This pattern is the standard way to test concurrent code synchronously.

Practice Convert methods to suspend

Keep all existing definitions. Add suspend before UserRepo.save, UserRepo.find and Api.route. Turn main into fun main() = runBlocking { }; move repo.save(User(1, "Alex")) and println(api.route("/users/1")) into it. Add import kotlinx.coroutines.runBlocking.

Starter code

Main.kt
data class User(val id: Int, val name: String)

object UsersTable {
    val rows = mutableListOf<User>()
    fun insert(u: User) { rows.add(u) }
    fun select(id: Int): User? = rows.find { it.id == id }
}

class UserRepo {
    fun save(u: User) { UsersTable.insert(u) }
    fun find(id: Int): User? = UsersTable.select(id)
}

class Api(val repo: UserRepo) {
    fun route(path: String): String {
        return when {
            path.startsWith("/users/") -> {
                val id = path.removePrefix("/users/").toInt()
                repo.find(id)?.name ?: "not found"
            }
            else -> "404"
        }
    }
}

fun main() {
    val repo = UserRepo()
    repo.save(User(1, "Alex"))
    val api = Api(repo)
    println(api.route("/users/1"))
}

// make save, find, route methods suspend
// open main with runBlocking

Sample solution

Main.kt
import kotlinx.coroutines.runBlocking

data class User(val id: Int, val name: String)

object UsersTable {
    val rows = mutableListOf<User>()
    fun insert(u: User) { rows.add(u) }
    fun select(id: Int): User? = rows.find { it.id == id }
}

class UserRepo {
    suspend fun save(u: User) { UsersTable.insert(u) }
    suspend fun find(id: Int): User? = UsersTable.select(id)
}

class Api(val repo: UserRepo) {
    suspend fun route(path: String): String {
        return when {
            path.startsWith("/users/") -> {
                val id = path.removePrefix("/users/").toInt()
                repo.find(id)?.name ?: "not found"
            }
            else -> "404"
        }
    }
}

fun main() = runBlocking {
    val repo = UserRepo()
    repo.save(User(1, "Alex"))
    val api = Api(repo)
    println(api.route("/users/1"))
}

Expected output

Alex

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 How is a suspend function called in main?

  • Inside runBlocking Correct answer
  • With Thread.start
  • With a Timer
  • Directly

Explanation: A suspend function is called inside a coroutine like runBlocking.

2 Match the coroutines parts

Term Meaning
suspend fun Awaitable function
runBlocking Scope opening
repo.save(...) Call
main = runBlocking Entry point

3 Complete the suspend function definition

Question
class UserRepo {
    ____ fun save(u: User) { UsersTable.insert(u) }
}
Answer
class UserRepo {
    suspend fun save(u: User) { UsersTable.insert(u) }
}

Explanation: suspend fun save is called inside a coroutine.

All lessons in Advanced