Arsitektur Concurrency Android Modern: Membedah StateFlow vs SharedFlow pada Kotlin Coroutines
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 strategionBufferOverflow. - 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.