Sejauh ini kita memakai Channel dan SharedFlow — keduanya hot stream yang aktif terlepas dari collector. Bab ini mengubah perspektif: Flow<T> adalah stream cold yang baru menghasilkan nilai saat di-collect. Setiap kali sebuah Flow di-collect, blok builder dieksekusi dari awal. Karakteristik ini membuat Flow cocok untuk transformasi data, sumber yang bisa dimulai ulang, dan pipeline fungsional. Bab ini memperkenalkan builder flow, semantik lazy, dan konversi dari koleksi reguler ke Flow.
Apa itu Flow #
Flow<T> adalah antarmuka untuk stream cold asynchronous. Tiga ciri pembeda:
| Ciri | Detail |
|---|---|
| Cold | Blok builder dieksekusi saat collect dipanggil, bukan saat builder dipanggil |
| Single execution | Setiap collect menjalankan blok dari awal — tidak ada state antar-collector |
| Sequential | Emit dijalankan berurutan; emit berikutnya menunggu collector memproses |
Kontras dengan SharedFlow:
| Aspek | Flow (cold) | SharedFlow (hot) |
|---|---|---|
| Mulai | Saat di-collect | Saat di-emit (atau saat dibuat jika ada collector) |
| Eksekusi | Per collector (baru setiap kali) | Shared antar collector |
| Replay | Tidak ada | Configurable |
| Cocok untuk | Sumber data, pipeline fungsional | Event broadcast, state |
Builder flow { emit(…) } #
Cara utama membuat Flow adalah dengan builder flow { ... }:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// 1. Builder flow { emit(...) }: cold stream
println("=== 1. Flow cold: dieksekusi saat di-collect ===")
val angkaFlow = flow {
println("[flow] mulai emit")
repeat(3) { i ->
println("[flow] emit $i")
emit(i)
}
println("[flow] selesai emit")
}
println("[main] sebelum collect pertama")
angkaFlow.collect { println("[collect-1] terima: $it") }
println()
println("[main] sebelum collect kedua (flow dieksekusi ulang)")
angkaFlow.collect { println("[collect-2] terima: $it") }
// 2. flowOf dan operator dasar
println()
println("=== 2. flowOf + map + filter ===")
flowOf(1, 2, 3, 4, 5)
.filter { it % 2 == 1 }
.map { it * 10 }
.collect { println("[hasil] $it") }
// 3. asFlow: konversi collection ke Flow
println()
println("=== 3. asFlow: List -> Flow ===")
listOf("A", "B", "C").asFlow()
.map { it.lowercase() }
.collect { println("[lower] $it") }
}Output yang Dihasilkan:
=== 1. Flow cold: dieksekusi saat di-collect ===
[main] sebelum collect pertama
[flow] mulai emit
[flow] emit 0
[collect-1] terima: 0
[flow] emit 1
[collect-1] terima: 1
[flow] emit 2
[collect-1] terima: 2
[flow] selesai emit
[main] sebelum collect kedua (flow dieksekusi ulang)
[flow] mulai emit
[flow] emit 0
[collect-2] terima: 0
[flow] emit 1
[collect-2] terima: 1
[flow] emit 2
[collect-2] terima: 2
[flow] selesai emit
=== 2. flowOf + map + filter ===
[hasil] 10
[hasil] 30
[hasil] 50
=== 3. asFlow: List -> Flow ===
[lower] a
[lower] b
[lower] cMari kita telaah baris-baris kode di atas:
- Baris 7-13 (
flow { ... }): builder membuatFlow<Int>. Penting: blok di dalamflow { ... }tidak dieksekusi saat builder dipanggil — baru dijalankan saatcollectdipanggil. Output membuktikan:[main] sebelum collect pertamamuncul sebelum[flow] mulai emit. - Baris 16 (
angkaFlow.collect { ... }): memulai eksekusi blok flow. Untuk setiapemit(i), lambda collector dipanggil dengan nilaii. Pola output[flow] emit 0→[collect-1] terima: 0→[flow] emit 1menunjukkan sifat sequential — emit berikutnya menunggu collector selesai. - Baris 19 (
angkaFlow.collectKEDUA): flow dieksekusi ulang dari awal. Output menunjukkan[flow] mulai emitmuncul lagi — bukan lanjutan dari koleksi pertama. Inilah inti cold stream: setiap collect menjalankan blok dari awal. - Baris 26-29 (
flowOf(1,2,3,4,5).filter { ... }.map { ... }.collect { ... }): idiom pipeline fungsional.flowOfadalah shortcut untuk Flow dengan nilai literal.filtermenyaring nilai ganjil;mapmengalikan dengan 10. Output10, 30, 50adalah1*10, 3*10, 5*10. - Baris 35 (
listOf("A","B","C").asFlow()): extension functionasFlow()mengkonversi koleksi reguler menjadi Flow. Berguna untuk memanfaatkan operator Flow pada List/Sequence.
Operator Dasar #
Flow menyediakan operator fungsional seperti pada Collection. Yang paling sering dipakai:
| Operator | Perilaku |
|---|---|
map { it -> ... } | Transformasi satu-ke-satu |
filter { it -> Boolean } | Membuang nilai yang tidak memenuhi predikat |
take(n) | Ambil n nilai pertama, lalu batalkan flow |
drop(n) | Lewati n nilai pertama |
transform { it -> emit(...) } | Transformasi fleksibel — bisa emit 0, 1, atau banyak nilai per input |
onEach { it -> ... } | Side-effect per nilai, tanpa mengubah nilai |
onStart { ... } | Dipanggil sebelum flow mulai |
onCompletion { ... } | Dipanggil saat flow selesai (sukses atau gagal) |
flowOf(1, 2, 3)
.transform {
emit(it)
emit(it * 10) // dua emit per input
}
.collect { println(it) }
// Output: 1, 10, 2, 20, 3, 30Flow dan Coroutine Context #
Flow dieksekusi di konteks pemanggil collect. Artinya, jika Anda memanggil collect di Dispatchers.Main, blok builder flow juga akan berjalan di Dispatchers.Main. Untuk mengubah context eksekusi flow, gunakan operator flowOn:
flow { emit(ambilDariDisk()) } // CPU/IO berat
.flowOn(Dispatchers.IO) // pindah ke thread IO
.collect { /* tetap di thread pemanggil */ }Cold Stream: Keuntungan #
Sifat cold Flow memiliki beberapa keuntungan praktis:
- Lazy: tidak ada komputasi sampai dibutuhkan. Flow bisa dideklarasikan tanpa efek samping.
- Re-playable: collect ulang selalu memberi hasil yang sama (jika builder deterministik).
- Composable: operator seperti
map/filtermembentuk pipeline tanpa eksekusi — pipeline terjadi saat collect. - Single execution: tidak ada kebocoran state antar-collector — masing-masing mendapat salinan terpisah.
Referensi Lanjutan #
- Flow
— panduan resmi
Flow<T>— cold stream berbasissuspend - kotlinx.coroutines core
— API reference
kotlinx.coroutines.flow
Selanjutnya #
Sekarang setelah kita memahami dasar Flow, bab Flow Operators akan membahas operator transformasi lanjutan: combine, zip, flatMapConcat, flatMapMerge, flatMapLatest, debounce, dan distinctUntilChanged — yang memungkinkan pipeline Flow yang ekspresif untuk kasus nyata.