Coroutines
- Kotlin course
- Advanced
- Lesson 82/83
- 6 min
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
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 runBlockingSample solution
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
class UserRepo {
____ fun save(u: User) { UsersTable.insert(u) }
}
class UserRepo {
suspend fun save(u: User) { UsersTable.insert(u) }
}
Explanation: suspend fun save is called inside a coroutine.