Arsitektur Concurrency Android Modern: Membedah StateFlow vs SharedFlow pada Kotlin Coroutines

Android Kotlin Flow Architecture
Sumber: Unsplash (Lisensi Bebas Hak Cipta Komersial)

Dalam rekayasa aplikasi Android modern berbasis Clean Architecture, pengelolaan status antarmuka pengguna (UI State) dan aliran data asinkron (asynchronous data streams) adalah fondasi stabilitas aplikasi. Sejak Google secara resmi merekomendasikan migrasi dari arsitektur lama berbasis LiveData menuju ekosistem Kotlin Coroutines dan Flow, para insinyur perangkat lunak dihadapkan pada keputusan desain arsitektural yang fundamental: kapan harus menggunakan StateFlow dan kapan harus menggunakan SharedFlow?

Kesalahan umum yang sering terjadi di level produksi adalah memperlakukan kedua tipe Hot Flow ini secara sembarangan, yang berujung pada hilangnya peristiwa satu kali (single-event drop), kebocoran memori lifecycle, atau konsumsi CPU berlebih di latar belakang saat Activity dalam keadaan onStop(). Di artikel rekayasa kali ini, kita akan membedah secara matematis dan praktis bagaimana arsitektur StateFlow vs SharedFlow bekerja di balik layar, penanganan mekanisme Backpressure, serta bagaimana mengamankan observasi state menggunakan API repeatOnLifecycle.


1. Anatomi Perbedaan: Hot Flow vs Cold Flow

Sebelum membedah StateFlow dan SharedFlow, kita harus memahami perbedaan mendasar antara Cold Stream dan Hot Stream:

  • Cold Flow (Standard Flow): Aliran data pasif yang baru dieksekusi ketika ada pengumpul (collector) yang memanggilnya. Setiap subscriber baru akan memicu eksekusi produser data dari awal (independen).
  • Hot Flow (StateFlow & SharedFlow): Aliran data aktif yang memancarkan data (emit) terlepas dari apakah ada subscriber yang mendengarkan atau tidak. Data dapat disiarkan (multicasting) ke banyak observer sekaligus.

2. Kapan Menggunakan StateFlow? (State Retention Architecture)

StateFlow dirancang khusus sebagai representasi status UI yang dapat diobservasi (Observable State Holder). Karakteristik utama StateFlow:

  • Selalu memiliki nilai awal (Initial Value) yang wajib didefinisikan saat inisialisasi.
  • Menyimpan nilai terakhir di memori (Replay Cache = 1). Subscriber baru yang terlambat mendaftar akan langsung menerima nilai terakhir ini seketika.
  • Menerapkan prinsip Conflation: Jika produser memancarkan nilai yang identik secara berurutan, StateFlow tidak akan memicu pembaruan ke UI (menggunakan perbandingan Any.equals()).

Implementasi Bersih StateFlow di ViewModel:

sealed interface UserUiState {
    object Loading : UserUiState
    data class Success(val userName: String, val balance: Long) : UserUiState
    data class Error(val message: String) : UserUiState
}

class DashboardViewModel(
    private val userRepository: UserRepository
) : ViewModel() {

    // Encapsulation: MutableStateFlow privat, StateFlow publik read-only
    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUserData() {
        viewModelScope.launch {
            _uiState.value = UserUiState.Loading
            try {
                val data = userRepository.fetchUser()
                _uiState.value = UserUiState.Success(data.name, data.balance)
            } catch (e: Exception) {
                _uiState.value = UserUiState.Error(e.localizedMessage ?: "Terjadi kesalahan")
            }
        }
    }
}

3. Kapan Menggunakan SharedFlow? (Event Driven Architecture)

SharedFlow adalah bentuk umum dari Hot Flow yang dirancang untuk menangani Events atau peristiwa satu kali (One-off Events), seperti menampilkan SnackBar, navigasi layar, atau memunculkan Toast notifikasi.

  • Tidak memerlukan nilai awal (Initial Value).
  • Ukuran cache replay dapat dikonfigurasi secara presisi melalui parameter replay, extraBufferCapacity, dan strategi onBufferOverflow.
  • Cocok untuk peristiwa yang tidak boleh diulang saat layar ponsel diputar (orientasi layar berubah).

Implementasi Navigasi Event Menggunakan SharedFlow:

sealed interface NavigationEvent {
    data class ToDetail(val userId: String) : NavigationEvent
    object Back : NavigationEvent
}

class ProfileViewModel : ViewModel() {

    private val _navEvent = MutableSharedFlow<NavigationEvent>(
        replay = 0, // Tidak menyimpan cache untuk subscriber baru
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val navEvent: SharedFlow<NavigationEvent> = _navEvent.asSharedFlow()

    fun onUserItemClicked(id: String) {
        viewModelScope.launch {
            _navEvent.emit(NavigationEvent.ToDetail(id))
        }
    }
}

4. Tabel Komparasi Teknis: StateFlow vs SharedFlow

Fitur Parameter StateFlow SharedFlow
Tujuan Utama Menyimpan status UI (State Holder) Menangani peristiwa satu kali (One-off Events)
Nilai Awal (Initial Value) Wajib ada Opsional / Fleksibel
Replay Cache Tepat 1 item terakhir Dikonfigurasi (0 s.d. n item)
Conflation (Deduplikasi) Aktif otomatis (nilai sama tidak di-emit) Tidak aktif (setiap emit pasti dikirim)

5. Pola Pengumpulan Aman Menggunakan repeatOnLifecycle

Mengumpulkan Hot Flow di dalam Activity atau Fragment tanpa pengawasan lifecycle akan mengakibatkan konsumsi baterai terus berjalan di background meskipun aplikasi di-minimize. Google merilis API repeatOnLifecycle untuk menghentikan pengumpulan data secara otomatis saat Activity berada di bawah batas state tertentu (misalnya STARTED).

class UserDashboardActivity : AppCompatActivity() {

    private val viewModel: DashboardViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dashboard)

        lifecycleScope.launch {
            // Pengumpulan otomatis dibatalkan saat Activity STOPPED dan dimulai ulang saat STARTED
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is UserUiState.Loading -> showProgressBar(true)
                        is UserUiState.Success -> renderUserContent(state)
                        is UserUiState.Error -> displayErrorMessage(state.message)
                    }
                }
            }
        }
    }
}

Pertanyaan Umum (FAQ Teknis)

Q: Apakah StateFlow sepenuhnya menggantikan LiveData?
A: Ya, dalam arsitektur modern Multiplatform dan Kotlin murni, StateFlow lebih direkomendasikan karena terintegrasi langsung dengan ekosistem Coroutines, mendukung operator Flow yang kaya, dan tidak terikat pada framework Android OS.

Q: Apa yang terjadi jika buffer SharedFlow penuh?
A: Anda dapat memilih opsi BufferOverflow.SUSPEND (menunda emisi hingga buffer kosong), BufferOverflow.DROP_OLDEST (menghapus data terlama), atau BufferOverflow.DROP_LATEST (mengabaikan data baru yang masuk) untuk menjaga stabilitas memori aplikasi.

RubrikPulsa.com menyajikan analisis rekayasa teknologi, arsitektur perangkat lunak, dan dokumentasi sistem berstandar profesional tinggi bagi komunitas pengembang di Indonesia.