Episode ini menguasai coroutines dan pemrograman asinkron di Kotlin: suspending functions, coroutine scopes dan dispatchers, launch dan async, structured concurrency dengan exception handling, serta Flow API untuk reactive streams dengan backpressure dan transformasi.

Episode 9 membawa kalian ke salah satu fitur paling penting di Kotlin modern: coroutines. Di episode 2 kalian sudah mendengar gambaran umumnya; sekarang saatnya praktik langsung dengan suspending functions, scopes, dispatchers, launch, async, structured concurrency, dan Flow.
Coroutines mengubah cara kalian menulis kode asinkron: alih-alih callback bersarang atau chain yang rumit, kalian menulis kode sekuensial yang bisa ditangguhkan tanpa memblokir thread. Ini dipakai di Android, backend, dan multiplatform.
Setelah episode ini, kalian akan menangani panggilan jaringan, operasi I/O, dan stream data dengan kode yang terbaca seperti kode sinkron.
Fungsi yang bisa ditangguhkan ditandai kata kunci suspend. Ketika memanggil operasi lambat seperti panggilan jaringan, coroutine menangguhkan dirinya — thread dibebaskan untuk pekerjaan lain — lalu melanjutkan eksekusi saat hasil siap:
suspend fun ambilData(): String {
delay(1000)
return "data siap"
}delay menangguhkan coroutine selama 1 detik tanpa memblokir thread. Fungsi suspend hanya bisa dipanggil dari coroutine atau fungsi suspend lain — compiler menegakkan batasan ini sehingga kalian tidak bisa lupa memakai scope.
Untuk menjalankan suspending function, kalian butuh builder coroutine seperti runBlocking (untuk main function dan test):
import kotlinx.coroutines.*
fun main() = runBlocking {
val data = ambilData()
println(data)
}runBlocking membuat bridge antara dunia blocking dan coroutine. Di production, kalian tidak memakai runBlocking untuk operasi nyata — melainkan scope aplikasi seperti CoroutineScope(Dispatchers.IO) yang dikelola lifecycle. Contoh di episode ini memerlukan dependency kotlinx-coroutines-core, yang bisa ditambahkan ke project Gradle lewat ./gradlew build.
Dispatcher menentukan thread tempat coroutine berjalan. Tiga yang paling umum:
val scope = CoroutineScope(Dispatchers.Default)
fun jalankan() {
scope.launch {
val hasil = withContext(Dispatchers.IO) {
// operasi I/O: membaca file, memanggil jaringan
"hasil I/O"
}
println(hasil)
}
}Dispatchers.Default untuk komputasi CPU-bound, Dispatchers.IO untuk operasi I/O, dan Dispatchers.Main di Android untuk pembaruan UI. withContext berpindah dispatcher di tengah coroutine tanpa kehilangan konteksnya.
launch memulai coroutine yang mengembalikan Job — cocok untuk pekerjaan tanpa hasil. async mengembalikan Deferred yang bisa diambil hasilnya dengan await, memungkinkan eksekusi paralel:
fun main() = runBlocking {
val a = async { hitungBerat(1) }
val b = async { hitungBerat(2) }
println("Total: ${a.await() + b.await()}")
}async untuk dua komputasi independen dijalankan secara paralel, dan await menunggu keduanya. Pola ini jauh lebih ringkas daripada mengelola thread atau future secara manual.
Structured concurrency menjamin bahwa coroutine tidak bisa bocor keluar dari scope induknya: jika parent dibatalkan, semua child ikut dibatalkan. Ini mencegah kebocoran pekerjaan latar yang sering menyebabkan bug memori:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try {
panggilApi()
} catch (e: Exception) {
println("Gagal: ${e.message}")
}
}SupervisorJob membuat kegagalan satu child tidak membatalkan sibling-nya. Gabungan try-catch di dalam coroutine dan SupervisorJob pada scope memberi kontrol granular atas kegagalan tanpa meruntuhkan seluruh hierarki.
Untuk penanganan global, gunakan CoroutineExceptionHandler pada scope:
val handler = CoroutineExceptionHandler { _, e ->
println("Coroutine gagal: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + handler)Handler menangkap exception yang tidak tertangani dari coroutine di scope tersebut. Kombinasi structured concurrency, SupervisorJob, dan handler membuat perilaku kegagalan predictable.
Flow adalah rekomendasi resmi untuk stream asinkron di Kotlin. Flow bersifat cold: produksi nilai baru dimulai saat dikoleksi. Transformasi seperti map dan filter bekerja seperti pada collection:
import kotlinx.coroutines.flow.*
fun hitungFlow(): Flow<Int> = flow {
for (i in 1..5) {
delay(500)
emit(i * i)
}
}
fun main() = runBlocking {
hitungFlow()
.filter { it % 2 == 0 }
.collect { println(it) }
}emit mengeluarkan nilai, dan collect mengonsumsinya. Operator filter di atas Flow menahan nilai ganjil. Flow juga mendukung map, flatMapConcat, take, dan operator lain untuk membangun pipeline pemrosesan data.
Di Android, Flow dipakai untuk data layer dengan StateFlow sebagai state holder yang reaktif. Di backend, Flow bagus untuk streaming data dan operasi yang menanggapi backpressure. Pemakaian Flow bersama coroutines akan kalian lihat lagi di episode 10 dan 11.
Episode 9 membuka kekuatan concurrency Kotlin: suspending functions yang tidak memblokir thread, scopes dan dispatchers untuk kontrol eksekusi, launch dan async untuk tugas paralel, structured concurrency untuk keamanan pembatalan, serta Flow untuk stream reaktif.
Inti yang harus dibawa pulang:
suspend function menangguhkan tanpa memblokir thread.runBlocking untuk main dan test; scope aplikasi untuk production.Dispatchers.Default untuk CPU, Dispatchers.IO untuk I/O.launch untuk tugas tanpa hasil; async plus await untuk hasil paralel.Di episode 10 selanjutnya kita akan membahas Kotlin for Android development — setup project Android dengan Kotlin, fitur khusus Android, ViewModel dan LiveData dengan coroutines, serta dasar Jetpack Compose dengan Kotlin DSL.