Untuk memegang state aplikasi yang selalu memiliki nilai (mis. counter, status login, konfigurasi), StateFlow adalah pilihan idiomatik Kotlin. Turunan khusus SharedFlow ini menjamin satu nilai saat ini, mengirimkan hanya perubahan kepada collector, dan tidak menyimpan history lengkap. Bab ini membahas konstruksi MutableStateFlow, perilaku conflation (nilai lama ditimpa sebelum collector sempat memproses), dan perbedaan dengan SharedFlow reguler.
Apa itu StateFlow #
StateFlow<T> adalah SharedFlow<T> dengan tiga jaminan:
| Jaminan | Detail |
|---|---|
| Selalu punya nilai | Memerlukan nilai awal saat konstruksi |
| Conflation | Hanya nilai terbaru yang dikirim ke collector baru; nilai antara yang belum diproses ditimpa |
| Distinct | Hanya emit saat nilai berubah (perbandingan equals) |
StateFlow dirancang untuk satu tujuan: memegang state. Producer mengganti state.value = x; semua collector aktif menerima x. Collector baru segera menerima nilai terbaru tanpa perlu menunggu emit berikutnya.
Dasar Penggunaan #
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// StateFlow selalu punya nilai awal
val state = MutableStateFlow(0)
println("[init] state.value=${state.value}")
// Collector A menerima nilai awal dan update
val jobA = launch {
state.collect { println("[A] state=$it") }
}
delay(50)
state.value = 10
delay(50)
state.value = 20
delay(50)
// Update cepat beruntun: hanya nilai terakhir yang terlihat collector
state.value = 30
state.value = 40
state.value = 50
delay(50)
jobA.cancel()
// Collector B mulai terlambat, hanya menerima nilai terbaru
println()
println("collector B mulai terlambat:")
val jobB = launch {
state.take(1).collect { println("[B] state=$it") }
}
delay(50)
jobB.cancel()
// Update value dengan operator assignment
state.value += 10
println("[akhir] state.value=${state.value}")
}Output yang Dihasilkan:
[init] state.value=0
[A] state=0
[A] state=10
[A] state=20
[A] state=50
collector B mulai terlambat:
[B] state=50
[akhir] state.value=60Mari kita telaah baris-baris kode di atas:
- Baris 6 (
MutableStateFlow(0)): membuat StateFlow dengan nilai awal0. Nilai awal wajib — tidak sepertiMutableSharedFlowyang bisa dibuat tanpa nilai. Setelah konstruksi,state.valueselalu tersedia tanpa harus meng-collect. - Baris 11 (
state.collect { ... }): collector A mulai. Karena StateFlow selalu punya nilai, collector segera menerima nilai awal (0). Output[A] state=0muncul sebelum pembaruan apa pun. - Baris 14-17 (
state.value = 10,delay(50), dst.): pembaruan nilai satu per satu dengan jeda. Setiap pembaruan dikirim ke collector A. Output menunjukkanstate=10lalustate=20. - Baris 20-22 (
state.value = 30; state.value = 40; state.value = 50): tiga pembaruan cepat beruntun tanpa delay. StateFlow melakukan conflation — nilai30dan40ditimpa sebelum collector sempat memproses. Collector hanya menerima50. Inilah inti conflation. - Baris 25 (
jobA.cancel()): collector A dihentikan. Setelah ini, pembaruan state tidak dikirim ke siapa pun — namunstate.valuetetap diperbarui. - Baris 30 (
state.take(1).collect { ... }): collector B baru mulai.take(1)membatasi ke satu nilai. Karena StateFlow selalu punya nilai terbaru, B menerima50(nilai terbaru saat B mulai). Inilah perbedaan kunci dengan SharedFlowreplay=0— StateFlow selalu memberi nilai awal ke collector baru. - Baris 34 (
state.value += 10): operator assignment+=bekerja karenavalueadalah propertivar. Setelah ini,state.value = 60.
Conflation: Karakteristik Kunci #
Conflation adalah perilaku fundamental StateFlow. Saat producer memperbarui value lebih cepat daripada collector bisa memproses, hanya nilai terbaru yang dikirim. Untuk state UI, ini biasanya diinginkan — pengguna tidak peduli dengan nilai antara yang sudah basi.
val state = MutableStateFlow(0)
launch {
state.collect { nilai ->
delay(100) // collector lambat
println("diproses: $nilai")
}
}
delay(50)
repeat(5) { state.value = it } // update cepat
// Output: diproses: 0, diproses: 4 (nilai antara ditimpa)StateFlow vs SharedFlow #
Perbedaan praktis antara StateFlow dan SharedFlow:
| Aspek | StateFlow | SharedFlow |
|---|---|---|
| Nilai awal | Wajib | Tidak ada |
| Replay | Selalu 1 (nilai terbaru) | Configurable |
| Conflation | Ya (otomatis) | Tidak (kecuali replay=1 + DROP_OLDEST) |
| Distinct emission | Ya (hanya saat equals berubah) | Tidak (semua emit dikirim) |
| Cocok untuk | State UI, konfigurasi, status | Event, broadcast, log |
Read-only StateFlow #
Untuk exposing state tanpa mengizinkan pemanggil memperbarui, gunakan StateFlow<T> (read-only) sebagai ganti MutableStateFlow<T>:
class Counter {
private val _state = MutableStateFlow(0)
val state: StateFlow<Int> = _state.asStateFlow() // read-only
fun increment() { _state.value++ }
}
val counter = Counter()
println(counter.state.value) // 0 — baca aman
// counter.state.value = 5 // compile error: StateFlow read-only
counter.increment()
println(counter.state.value) // 1Penggunaan di UI (Konseptual) #
Pola umum di aplikasi yang memakai StateFlow:
class UserViewModel {
private val _uiState = MutableStateFlow(UiState.loading())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun muatUser(id: String) {
_uiState.value = UiState.loading()
viewModelScope.launch {
_uiState.value = UiState.success(api.fetch(id))
}
}
}UI meng-collect uiState dan otomatis berubah setiap kali state berubah. Karena conflation, UI tidak perlu memproses setiap intermediate state.
Referensi Lanjutan #
- Flow
— panduan resmi
StateFlowuntuk state observable - kotlinx.coroutines core
— API reference
kotlinx.coroutines.flow.StateFlow
Selanjutnya #
StateFlow cocok untuk state yang selalu punya nilai. Untuk event broadcast (tanpa nilai awal, tanpa conflation), gunakan SharedFlow reguler. Bab SharedFlow akan membahas konfigurasi MutableSharedFlow, strategi buffer overflow, dan pola event bus.