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:

JaminanDetail
Selalu punya nilaiMemerlukan nilai awal saat konstruksi
ConflationHanya nilai terbaru yang dikirim ke collector baru; nilai antara yang belum diproses ditimpa
DistinctHanya 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 #

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

text
[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=60

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (MutableStateFlow(0)): membuat StateFlow dengan nilai awal 0. Nilai awal wajib — tidak seperti MutableSharedFlow yang bisa dibuat tanpa nilai. Setelah konstruksi, state.value selalu tersedia tanpa harus meng-collect.
  2. Baris 11 (state.collect { ... }): collector A mulai. Karena StateFlow selalu punya nilai, collector segera menerima nilai awal (0). Output [A] state=0 muncul sebelum pembaruan apa pun.
  3. Baris 14-17 (state.value = 10, delay(50), dst.): pembaruan nilai satu per satu dengan jeda. Setiap pembaruan dikirim ke collector A. Output menunjukkan state=10 lalu state=20.
  4. Baris 20-22 (state.value = 30; state.value = 40; state.value = 50): tiga pembaruan cepat beruntun tanpa delay. StateFlow melakukan conflation — nilai 30 dan 40 ditimpa sebelum collector sempat memproses. Collector hanya menerima 50. Inilah inti conflation.
  5. Baris 25 (jobA.cancel()): collector A dihentikan. Setelah ini, pembaruan state tidak dikirim ke siapa pun — namun state.value tetap diperbarui.
  6. Baris 30 (state.take(1).collect { ... }): collector B baru mulai. take(1) membatasi ke satu nilai. Karena StateFlow selalu punya nilai terbaru, B menerima 50 (nilai terbaru saat B mulai). Inilah perbedaan kunci dengan SharedFlow replay=0 — StateFlow selalu memberi nilai awal ke collector baru.
  7. Baris 34 (state.value += 10): operator assignment += bekerja karena value adalah properti var. 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.

kotlin Contoh: konflik conflation
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:

AspekStateFlowSharedFlow
Nilai awalWajibTidak ada
ReplaySelalu 1 (nilai terbaru)Configurable
ConflationYa (otomatis)Tidak (kecuali replay=1 + DROP_OLDEST)
Distinct emissionYa (hanya saat equals berubah)Tidak (semua emit dikirim)
Cocok untukState UI, konfigurasi, statusEvent, broadcast, log

Read-only StateFlow #

Untuk exposing state tanpa mengizinkan pemanggil memperbarui, gunakan StateFlow<T> (read-only) sebagai ganti MutableStateFlow<T>:

kotlin Pola: encapsulasi state di class
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)  // 1

Penggunaan di UI (Konseptual) #

Pola umum di aplikasi yang memakai StateFlow:

kotlin Pola: ViewModel dengan 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 #

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.