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:

CiriDetail
ColdBlok builder dieksekusi saat collect dipanggil, bukan saat builder dipanggil
Single executionSetiap collect menjalankan blok dari awal — tidak ada state antar-collector
SequentialEmit dijalankan berurutan; emit berikutnya menunggu collector memproses

Kontras dengan SharedFlow:

AspekFlow (cold)SharedFlow (hot)
MulaiSaat di-collectSaat di-emit (atau saat dibuat jika ada collector)
EksekusiPer collector (baru setiap kali)Shared antar collector
ReplayTidak adaConfigurable
Cocok untukSumber data, pipeline fungsionalEvent broadcast, state

Builder flow { emit(…) } #

Cara utama membuat Flow adalah dengan builder flow { ... }:

kotlin
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:

text
=== 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] c

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-13 (flow { ... }): builder membuat Flow<Int>. Penting: blok di dalam flow { ... } tidak dieksekusi saat builder dipanggil — baru dijalankan saat collect dipanggil. Output membuktikan: [main] sebelum collect pertama muncul sebelum [flow] mulai emit.
  2. Baris 16 (angkaFlow.collect { ... }): memulai eksekusi blok flow. Untuk setiap emit(i), lambda collector dipanggil dengan nilai i. Pola output [flow] emit 0 → [collect-1] terima: 0 → [flow] emit 1 menunjukkan sifat sequential — emit berikutnya menunggu collector selesai.
  3. Baris 19 (angkaFlow.collect KEDUA): flow dieksekusi ulang dari awal. Output menunjukkan [flow] mulai emit muncul lagi — bukan lanjutan dari koleksi pertama. Inilah inti cold stream: setiap collect menjalankan blok dari awal.
  4. Baris 26-29 (flowOf(1,2,3,4,5).filter { ... }.map { ... }.collect { ... }): idiom pipeline fungsional. flowOf adalah shortcut untuk Flow dengan nilai literal. filter menyaring nilai ganjil; map mengalikan dengan 10. Output 10, 30, 50 adalah 1*10, 3*10, 5*10.
  5. Baris 35 (listOf("A","B","C").asFlow()): extension function asFlow() 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:

OperatorPerilaku
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)
kotlin Contoh: transform fleksibel
flowOf(1, 2, 3)
    .transform {
        emit(it)
        emit(it * 10)  // dua emit per input
    }
    .collect { println(it) }
// Output: 1, 10, 2, 20, 3, 30

Flow 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:

kotlin Contoh: flowOn untuk pindah dispatcher
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:

  1. Lazy: tidak ada komputasi sampai dibutuhkan. Flow bisa dideklarasikan tanpa efek samping.
  2. Re-playable: collect ulang selalu memberi hasil yang sama (jika builder deterministik).
  3. Composable: operator seperti map/filter membentuk pipeline tanpa eksekusi — pipeline terjadi saat collect.
  4. Single execution: tidak ada kebocoran state antar-collector — masing-masing mendapat salinan terpisah.

Referensi Lanjutan #

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.