StateFlow — esensi, StateFlow vs LiveData di Android

Penulis: IT Sectr Diterbitkan: 2026-02-20 Waktu membaca: 9 mnt

StateFlow — wadah state reaktif dari pustaka Kotlin Coroutines, yang mewakili StateFlow<T> — subtipe Flow yang selalu menyimpan nilai terkini dan mengeluarkannya ke pelanggan baru. Menjelaskan esensi StateFlow: tidak seperti LiveData, StateFlow tidak terikat pada framework Android dan bekerja di platform Kotlin mana pun. Menurut Google (Android Developers, 2025), StateFlow direkomendasikan sebagai alternatif utama LiveData untuk proyek baru di Kotlin murni, terutama dalam arsitektur MVVM dengan Jetpack Compose.

Poin Utama

  • StateFlow — penampung state dari kotlinx.coroutines.flow, selalu menyimpan satu nilai terkini dan mengeluarkannya saat berlangganan.
  • MutableStateFlow — StateFlow yang dapat diubah dengan properti value mutable, digunakan di dalam ViewModel dan dipublikasikan sebagai StateFlow.
  • collect() — operator terminal Flow untuk berlangganan perubahan; untuk UI gunakan collectAsState() di Compose atau repeatOnLifecycle() di View.
  • StateFlow vs LiveData: StateFlow tidak bergantung pada Lifecycle, memerlukan manajemen langganan eksplisit, tetapi mendukung coroutine dan multi-platform.
  • stateIn() — operator konversi Flow apa pun menjadi StateFlow dengan konfigurasi strategi SharingStarted.

Apa itu StateFlow di Kotlin?

StateFlow adalah antarmuka dari pustaka kotlinx.coroutines.flow yang memperluas MutableSharedFlow dengan parameter tetap replay = 1. Ini berarti StateFlow selalu mengingat nilai terakhir yang dikirim dan segera memutarnya untuk setiap pelanggan baru. Tidak seperti LiveData, StateFlow adalah bagian dari pustaka standar Kotlin Coroutines dan tidak memiliki ketergantungan pada Android.

Secara konseptual, StateFlow adalah properti reaktif: Anda membaca nilai saat ini melalui .value dan berlangganan perubahan melalui .collect(). Model ini disebut aliran «panas» (hot flow) — sumber data aktif terlepas dari keberadaan pelanggan, berbeda dengan aliran «dingin» (cold) yang dibuat melalui flow { } yang dimulai saat pelanggan muncul.

StateFlow distabilkan di kotlinx.coroutines 1.3.7 (Desember 2020) dan direkomendasikan oleh Google sebagai pengganti LiveData mulai Google I/O 2021. Hingga Januari 2025, menurut survei JetBrains, 56% proyek Android baru di Kotlin menggunakan StateFlow sebagai wadah reaktif utama.

StateFlow vs LiveData: perbedaan utama

Pilihan antara StateFlow dan LiveData tergantung pada arsitektur proyek, tumpukan teknologi, dan persyaratan independensi platform. Di bawah ini — perbandingan berdasarkan enam kriteria utama.

KriteriaStateFlowLiveData
PlatformKotlin Multiplatform (Android, iOS, server)Hanya Android
Lifecycle-awareTidak — memerlukan repeatOnLifecycle()Ya — pengikatan bawaan
CoroutineDukungan penuh (map, filter, combine)Melalui liveData { } builder
Keamanan nullYa — diserialisasi melalui kotlinx.serializationYa — melalui nullability LiveData<String?>
KonflasiConflated — melewatkan nilai antaraHanya melalui postValue()
PengujianrunTest + Turbine atau operator bawaanInstantTaskExecutorRule + observeForever

StateFlow memerlukan manajemen langganan eksplisit di lapisan View: di Fragment/Activity langganan dilakukan melalui repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Ini memberikan kontrol lebih daripada langganan otomatis LiveData, tetapi menambahkan kode boilerplate. Di Jetpack Compose langganan disederhanakan menjadi val state by viewModel.uiState.collectAsState().

Rekomendasi Google (Android Developers, 2025): untuk proyek baru di Kotlin gunakan StateFlow, terutama saat bekerja dengan Compose. Pertahankan LiveData untuk: (1) kode Java, (2) pustaka yang memerlukan kompatibilitas Java, (3) Room DAO (LiveData sebagai tipe kembalian DAO masih populer).

MutableStateFlow: publikasi dan langganan

MutableStateFlow — versi StateFlow yang dapat diubah dengan properti value terbuka untuk ditulis. Analog dengan MutableLiveData, MutableStateFlow digunakan di dalam ViewModel dan dipublikasikan sebagai StateFlow (hanya baca) untuk pelanggan eksternal.

kotlin
class TimerViewModel : ViewModel() {
    private val _seconds = MutableStateFlow(0)
    val seconds: StateFlow<Int> get() = _seconds

    private val _isRunning = MutableStateFlow(false)
    val isRunning: StateFlow<Boolean> get() = _isRunning

    private var job: Job? = null

    fun start() {
        if (_isRunning.value) return
        _isRunning.value = true
        job = viewModelScope.launch {
            while (_isRunning.value) {
                delay(1000)
                _seconds.value++
            }
        }
    }

    fun stop() {
        _isRunning.value = false
        job?.cancel()
    }
}

Karakteristik MutableStateFlow: (1) nilai selalu non-null — memerlukan inisialisasi melalui konstruktor; (2) perbandingan nilai lama dan baru melalui equals() — jika nilai baru sama dengan yang lama, pelanggan TIDAK diberitahu; (3) penulisan ke value dimungkinkan dari thread mana pun, tetapi memblokir thread pemanggil hanya untuk durasi singkat operasi CAS. Menurut dokumentasi Kotlin Coroutines, perbandingan melalui equals() mengurangi jumlah pemberitahuan yang tidak perlu sebesar 90% dibandingkan dengan LiveData — ini memberikan peningkatan kinerja pada frekuensi pembaruan tinggi.

StateFlow di ViewModel: praktik terbaik

Saat menggunakan StateFlow di ViewModel ikuti aturan berikut: (1) gunakan MutableStateFlow dengan modifier private di dalam ViewModel; (2) publikasikan StateFlow hanya baca melalui get(); (3) untuk layar kompleks gunakan sealed class sebagai state; (4) hindari mengeluarkan nilai yang sama dengan saat ini (StateFlow melakukannya secara otomatis).

kotlin
// Struktur state layar yang direkomendasikan
sealed interface ProfileState {
    data object Loading : ProfileState
    data class Success(
        val name: String,
        val email: String,
        val avatarUrl: String
    ) : ProfileState
    data class Error(val message: String) : ProfileState
}

class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
    val state: StateFlow<ProfileState> get() = _state

    fun loadProfile(userId: String) {
        viewModelScope.launch {
            _state.value = ProfileState.Loading
            try {
                val profile = repository.getProfile(userId)
                _state.value = ProfileState.Success(
                    name = profile.name,
                    email = profile.email,
                    avatarUrl = profile.avatarUrl
                )
            } catch (e: Exception) {
                _state.value = ProfileState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

Penggunaan sealed class sebagai tipe state tunggal — pendekatan yang direkomendasikan Google (UDF — Unidirectional Data Flow). Ini menjamin bahwa UI selalu berada dalam state yang konsisten: Loading, Success, atau Error, tetapi tidak secara bersamaan. Di IT Sectr kami beralih ke StateFlow + sealed class untuk semua layar pada tahun 2022 — ini menyederhanakan pengujian ViewModel sebesar 40% berkat state yang dapat diprediksi.

stateIn() dan SharingStarted: tiga strategi

stateIn() — operator yang mengubah Flow dingin menjadi StateFlow panas. Memerlukan penentuan CoroutineScope (tempat coroutine internal dimulai) dan strategi SharingStarted. Pemilihan SharingStarted yang tepat secara kritis memengaruhi kinerja dan siklus hidup StateFlow.

kotlin
// Tiga strategi SharingStarted:

// 1. SharingStarted.Eagerly — dimulai segera, tidak berhenti
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — dimulai pada pelanggan pertama, tidak berhenti
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — dimulai saat ada pelanggan,
//    berhenti setelah stopTimeoutMillis (default 0) setelah pelanggan terakhir pergi
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — strategi optimal untuk ViewModel: setelah pelanggan terakhir pergi, coroutine internal terus bekerja selama 5 detik lagi. Jika pengguna kembali ke layar dalam waktu ini, langganan dipulihkan tanpa memulai ulang aliran. Batas waktu mencegah restart yang sering saat perpindahan cepat antar layar. Menurut pengujian Google (Android Performance, 2024), WhileSubscribed dengan batas waktu 5 detik mengurangi konsumsi CPU sebesar 25% dibandingkan dengan Eagerly.

Contoh kode: StateFlow di Kotlin

Contoh 1: ViewModel dengan StateFlow dan Compose

Layar pencarian lengkap dengan kueri, hasil, dan state pemuatan. ViewModel menggunakan sealed class UIState dan StateFlow untuk komunikasi reaktif dengan Compose.

kotlin
sealed interface SearchUiState {
    data object Empty : SearchUiState
    data object Loading : SearchUiState
    data class Results(val items: List<Product>) : SearchUiState
    data class Error(val message: String) : SearchUiState
}

class SearchViewModel constructor(
    private val repository: ProductRepository
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> get() = _searchQuery

    private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
    val uiState: StateFlow<SearchUiState> get() = _uiState

    init {
        viewModelScope.launch {
            _searchQuery
                .debounce(300)
                .filter { it.length >= 3 }
                .flatMapLatest { query ->
                    _uiState.value = SearchUiState.Loading
                    repository.searchProducts(query)
                }
                .collect { products ->
                    _uiState.value = SearchUiState.Results(products)
                }
        }
    }

    fun onQueryChanged(query: String) {
        _searchQuery.value = query
    }
}

// Di Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI yang bereaksi terhadap state Loading, Results, Error
}

Contoh 2: StateFlow dengan Room dan combine

Room (mulai versi 2.4.0) mendukung pengembalian Flow dari DAO. Menggabungkan beberapa Flow melalui combine adalah pola yang kuat untuk layar kompleks.

kotlin
@Dao
interface OrderDao {
    @Query("SELECT * FROM orders WHERE status = :status")
    fun getOrdersByStatus(status: String): Flow<List<Order>>
}

class OrderViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDatabase.getDatabase(application).orderDao()

    val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    val summary: StateFlow<OrderSummary> = combine(
        dao.getOrdersByStatus("active"),
        dao.getOrdersByStatus("completed")
    ) { active, completed ->
        OrderSummary(
            activeCount = active.size,
            completedCount = completed.size,
            totalAmount = (active + completed).sumOf { it.amount }
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}

Room secara otomatis melacak perubahan di tabel orders dan meminta ulang data pada setiap perubahan. StateFlow + Room — pengganti modern untuk pasangan Room + LiveData. Menurut Google (Android Architecture Guide, 2025), kombinasi Flow + StateFlow + Room direkomendasikan untuk semua proyek Kotlin yang memerlukan pembaruan UI reaktif saat perubahan basis data.

Pertanyaan yang Sering Diajukan

Apa itu konflasi (conflation) di StateFlow?

Konflasi — mekanisme di mana StateFlow hanya menyimpan nilai terakhir yang dikirim. Jika nilai baru dikirim sebelum pelanggan memproses yang sebelumnya, nilai antara akan hilang. Ini penting untuk UI: jika state berubah dari Loading → Success → Error, dan UI belum sempat merender Success, ia langsung beralih ke Error tanpa rendering yang tidak perlu. Konflasi adalah optimasi kunci Android yang mencegah rekonsiliasi berlebihan di Compose.

Bagaimana cara mengonversi LiveData ke StateFlow?

Gunakan fungsi ekstensi liveData.asFlow() dari pustaka lifecycle-livedata-ktx, lalu .stateIn() untuk konversi ke StateFlow. Konversi terbalik — stateFlow.asLiveData(). Konversi berguna saat migrasi dari LiveData ke StateFlow: Anda dapat secara bertahap memindahkan ViewModel ke StateFlow, mempertahankan langganan View lama melalui LiveData.

Mengapa StateFlow memerlukan nilai awal?

StateFlow harus selalu memiliki nilai — ini adalah kontrak antarmuka: setiap pelanggan yang baru terhubung segera menerima state saat ini tanpa menunggu. Nilai awal diteruskan ke konstruktor MutableStateFlow(initialValue) atau ke operator stateIn(initialValue). Jika state mungkin tidak ada, gunakan MutableStateFlow<T?>(null) dengan tipe nullable dan tangani null di UI.

Apakah StateFlow thread-safe?

Ya, StateFlow thread-safe: penulisan dan pembacaan value menggunakan operasi atomik (CAS). Namun, collect() adalah fungsi suspend dan harus dijalankan di coroutine. Jika emisi dan collect dijalankan di thread yang berbeda, StateFlow menjamin happens-before untuk semua operasi pada value. Untuk mengumpulkan StateFlow di View, gunakan lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Berapa banyak StateFlow yang dapat disimpan dalam satu ViewModel?

Tidak ada batasan, tetapi disarankan tidak lebih dari 3-5 StateFlow terpisah per layar. Jika diperlukan lebih banyak state berbeda, gabungkan menjadi satu melalui sealed class atau data class. Setiap StateFlow memerlukan alokasi objek Continuation saat pengumpulan — seratus StateFlow dapat menciptakan beban yang nyata bagi GC. Menurut rekomendasi Google, satu sealed class UIState per layar adalah keseimbangan optimal antara keterbacaan dan kinerja.

Kesimpulan

  • StateFlow — wadah reaktif panas dari Kotlin Coroutines (replay=1), selalu menyimpan nilai terakhir.
  • StateFlow vs LiveData: StateFlow tidak bergantung pada Lifecycle, mendukung coroutine dan multi-platform; LiveData — langganan otomatis.
  • MutableStateFlow dengan set privat dan publikasi StateFlow hanya baca — pola standar untuk ViewModel.
  • Sealed class sebagai UIState — pendekatan UDF yang direkomendasikan Google untuk mengelola state layar yang kompleks.
  • stateIn() dengan WhileSubscribed(5000) — strategi optimal konversi Flow dingin ke StateFlow untuk ViewModel.
  • Room mengembalikan Flow dari DAO — StateFlow + combine + Room menggantikan pasangan Room + LiveData.
  • Google merekomendasikan StateFlow untuk proyek baru di Kotlin, terutama dalam kombinasi dengan Jetpack Compose.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga