Sejauh ini kita sudah memakai Flow, SharedFlow, dan StateFlow tanpa benar-benar membedakan kapan memakai yang mana. Bab ini memperjelas perbedaan fundamental antara cold dan hot stream di Kotlin, menjelaskan mengapa Flow dijalankan ulang setiap kali di-collect sementara SharedFlow selalu aktif terlepas dari collector. Pemahaman ini krusial untuk memilih abstraksi yang tepat saat merancang state atau event di aplikasi nyata.
Apa itu Cold Stream #
Cold stream adalah stream yang baru mulai menghasilkan nilai saat di-collect. Setiap kali sebuah cold Flow di-collect, blok builder dieksekusi dari awal. Tidak ada state yang dibagikan antar collector — masing-masing mendapat salinan terpisah.
Karakteristik cold Flow:
- Lazy: tidak ada komputasi sampai ada collector.
- Re-playable: collect ulang selalu memberi hasil yang sama (jika builder deterministik).
- Single execution: setiap collect menjalankan blok dari awal.
- Tidak ada efek samping sampai di-collect.
Apa itu Hot Stream #
Hot stream adalah stream yang aktif terlepas dari collector. SharedFlow dan StateFlow adalah implementasi hot stream di kotlinx.coroutines. Saat sebuah nilai di-emit, semua collector yang aktif akan menerimanya — tetapi collector yang belum mendaftar akan kehilangan nilai yang sudah lewat.
Karakteristik hot stream:
- Aktif sebelum collector: emit dapat terjadi bahkan tanpa collector.
- Shared antar collector: setiap nilai dipancarkan ke semua collector aktif.
- Stateful: hot stream bisa punya nilai terbaru (
StateFlow) atau tidak (SharedFlow).
Perbandingan Praktis #
Tabel berikut merangkum perbedaan utama:
| Aspek | Flow (cold) | SharedFlow (hot) | StateFlow (hot) |
|---|---|---|---|
| Mulai | Saat di-collect | Saat di-emit | Saat di-emit atau dibuat |
| Eksekusi | Per collector | Shared antar collector | Shared antar collector |
| Replay | Tidak ada | Configurable (default 0) | Selalu 1 (nilai terbaru) |
| Nilai awal | Tidak ada | Tidak ada (kecuali replay > 0) | Wajib ada |
| Cocok untuk | Sumber data, pipeline | Event broadcast | State UI |
Demo: Cold Flow dieksekusi ulang #
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
println("=== 1. Cold Flow: dieksekusi ulang tiap collect ===")
val coldFlow = flow {
println("[cold] mulai emit")
emit("A")
emit("B")
emit("C")
println("[cold] selesai emit")
}
println("-- collect pertama --")
coldFlow.collect { println(" terima: $it") }
println("-- collect kedua (flow dijalankan ulang) --")
coldFlow.collect { println(" terima: $it") }
println()
println("=== 2. Hot SharedFlow: nilai dibagikan ke semua collector ===")
val shared = MutableSharedFlow<String>(replay = 0)
val jobPemancar = launch {
var i = 0
while (true) {
val pesan = "pesan-${i}"
println("[pemancar] kirim $pesan")
shared.emit(pesan)
delay(50)
i++
}
}
// Collector pertama
val jobCollector1 = launch {
shared.collect { println(" [c1] terima: $it") }
}
delay(80) // biarkan beberapa pesan masuk ke collector 1
// Collector kedua bergabung belakangan
println("-- collector 2 bergabung --")
val jobCollector2 = launch {
shared.collect { println(" [c2] terima: $it") }
}
delay(120)
jobCollector1.cancel()
jobCollector2.cancel()
jobPemancar.cancel()
println("[main] semua job dihentikan")
println()
println("=== 3. Hot StateFlow: selalu punya nilai terbaru ===")
val state = MutableStateFlow(0)
println("[main] state awal = ${state.value}")
val jobState = launch {
state.collect { println(" [state] nilai = $it") }
}
delay(30)
state.value = 10
delay(30)
state.value = 20
delay(30)
jobState.cancel()
println("[main] state akhir = ${state.value}")
println()
println("=== 4. Cold Flow: tidak ada efek samping sampai di-collect ===")
var jumlahEmit = 0
val flowPemalas = flow {
jumlahEmit++
emit(jumlahEmit)
}
println("[main] sebelum collect, jumlahEmit = $jumlahEmit")
flowPemalas.collect { println(" [collect] terima $it") }
println("[main] setelah collect pertama, jumlahEmit = $jumlahEmit")
flowPemalas.collect { println(" [collect] terima $it") }
println("[main] setelah collect kedua, jumlahEmit = $jumlahEmit")
}Output yang Dihasilkan:
=== 1. Cold Flow: dieksekusi ulang tiap collect ===
-- collect pertama --
[cold] mulai emit
terima: A
terima: B
terima: C
[cold] selesai emit
-- collect kedua (flow dijalankan ulang) --
[cold] mulai emit
terima: A
terima: B
terima: C
[cold] selesai emit
=== 2. Hot SharedFlow: nilai dibagikan ke semua collector ===
[pemancar] kirim pesan-0
[pemancar] kirim pesan-1
[c1] terima: pesan-1
-- collector 2 bergabung --
[pemancar] kirim pesan-2
[c1] terima: pesan-2
[c2] terima: pesan-2
[pemancar] kirim pesan-3
[c1] terima: pesan-3
[c2] terima: pesan-3
[main] semua job dihentikan
=== 3. Hot StateFlow: selalu punya nilai terbaru ===
[main] state awal = 0
[state] nilai = 0
[state] nilai = 10
[state] nilai = 20
[main] state akhir = 20
=== 4. Cold Flow: tidak ada efek samping sampai di-collect ===
[main] sebelum collect, jumlahEmit = 0
[collect] terima 1
[main] setelah collect pertama, jumlahEmit = 1
[collect] terima 2
[main] setelah collect kedua, jumlahEmit = 2Mari kita bedah bagian penting dari kode di atas:
- Bagian 1 (Cold Flow di-collect dua kali): output menunjukkan blok
flow { }dijalankan ulang pada collect kedua. Pesan[cold] mulai emitmuncul dua kali — sekali untuk setiap collect. Tidak ada state yang dipertahankan antar collect. - Bagian 2 (SharedFlow dengan dua collector): pemancar memancarkan pesan tiap 50ms. Collector 1 mendaftar pertama dan menerima
pesan-1,pesan-2, dst. Collector 2 bergabung belakangan dan hanya menerima pesan yang dipancarkan setelah ia mendaftar.pesan-0hilang untuk Collector 2 karenareplay = 0— inilah inti hot stream: nilai yang sudah lewat tidak diulang. - Bagian 3 (StateFlow dengan nilai terbaru): saat collector mendaftar, ia langsung menerima nilai terbaru (
0). Setiap updatestate.value = 10danstate.value = 20langsung diterima collector.state.valuejuga bisa dibaca langsung tanpa collect — fitur penting StateFlow untuk membaca state sinkron. - Bagian 4 (Efek samping cold Flow): variabel
jumlahEmithanya bertambah saat Flow di-collect. Sebelum collect, nilainya0. Setelah collect pertama, nilainya1. Setelah collect kedua, nilainya2. Ini membuktikan bahwa builder Flow benar-benar lazy — efek samping tidak terjadi sampai collect dipanggil.
Kapan Memakai Cold Flow #
Cold Flow cocok untuk:
- Sumber data yang bisa dimulai ulang: query database, request HTTP, baca file. Setiap collect memicu eksekusi baru.
- Pipeline transformasi fungsional:
flowOf(...).map { }.filter { }.collect { }— sifat lazy membuat pipeline mudah dibaca. - Operasi yang harus deterministik: bila hasil collect harus sama setiap kali (jika input sama), Flow adalah pilihan yang tepat.
- Resource cleanup otomatis: Flow yang dihentikan (cancel) otomatis membersihkan resource di akhir blok builder.
Kapan Memakai Hot Stream #
Hot stream cocok untuk:
- Event broadcast: tombol diklik, pesan WebSocket masuk, perubahan preferensi. Multiple subscriber menerima event yang sama.
- State UI yang berubah:
StateFlowadalah pilihan standar di ViewModel Android untuk menampung state UI. - Sinkronisasi antar komponen: hot stream memungkinkan komponen berbeda membaca state yang sama.
- Sumber yang aktif terus-menerus: sensor, timer, atau queue yang aktif bahkan tanpa subscriber.
SharedFlow vs StateFlow #
StateFlow adalah spesialisasi SharedFlow dengan replay = 1 dan conflation otomatis (nilai baru menimpa nilai lama yang belum dikonsumsi). Perbandingan:
| Aspek | SharedFlow | StateFlow |
|---|---|---|
| Nilai awal | Opsional | Wajib |
| Replay | Configurable | Selalu 1 |
| Conflation | Manual via conflate() | Otomatis |
| Akses nilai tanpa collect | Tidak (value tidak ada) | Ya (state.value) |
| Idempotency | Emit sama tetap dipancarkan | Emit nilai sama diabaikan |
Referensi Lanjutan #
- Flow
— pembedaan
Flow(cold) vsSharedFlow/StateFlow(hot) - Channels
— alternatif hot stream via
Channel
Selanjutnya #
Sekarang setelah kita paham kapan memakai Flow vs SharedFlow vs StateFlow, bab berikutnya akan membahas cara menguji Flow. Kita akan melihat pola flow.toList(...) untuk menangkap semua emit ke List, lalu memverifikasi urutan dan isi dengan assertion. Pendekatan ini memungkinkan pengujian Flow tanpa dependensi tambahan.