SharedFlow: apa itu, SharedFlow vs StateFlow di Android

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

SharedFlow — aliran reaktif panas dari pustaka Kotlin Coroutines, dioptimalkan untuk event satu kali (one-shot events) yang tidak boleh berulang saat rotasi layar atau pembuatan ulang pelanggan. Kami menunjukkan perbedaan SharedFlow dengan StateFlow: tidak seperti StateFlow, SharedFlow tidak menyimpan nilai terakhir untuk pelanggan baru dan mendukung konfigurasi replay, extraBufferCapacity, dan onBufferOverflow. Menurut Google (Android Developers, 2025), SharedFlow adalah solusi yang direkomendasikan untuk perintah navigasi, pesan Snackbar, dan event lain yang harus diproses tepat satu kali.

Poin Utama

  • SharedFlow — hot flow untuk event satu kali: pelanggan baru tidak menerima nilai sebelumnya tanpa pengaturan replay.
  • MutableSharedFlow — versi yang dapat diubah dengan metode emit() dan tryEmit() untuk mengirim event.
  • SharedFlow vs StateFlow: SharedFlow tidak menggabungkan nilai (dapat mem-buffer beberapa), tidak memerlukan nilai awal, cocok untuk event satu kali.
  • replay — jumlah event terakhir yang diputar ulang ke pelanggan baru (default 0).
  • extraBufferCapacity — buffer tambahan untuk event di luar replay, mencegah pemblokiran emit().

Apa itu SharedFlow di Kotlin?

SharedFlow — adalah aliran panas (hot flow) dari pustaka kotlinx.coroutines.flow yang, tidak seperti StateFlow, tidak terikat pada satu status dan dapat mengirimkan sejumlah event ke pelanggan mana pun. SharedFlow adalah tipe dasar untuk StateFlow — StateFlow diimplementasikan melalui SharedFlow dengan replay = 1.

Fitur utama SharedFlow — tidak wajib menyimpan nilai terakhir. Secara default (replay = 0), pelanggan baru tidak menerima apa pun sampai event baru dikirim. Ini membuat SharedFlow ideal untuk skenario di mana event harus diproses tepat satu kali: navigasi, Snackbar, notifikasi sistem, hasil pemindaian kode QR.

SharedFlow distabilkan di kotlinx.coroutines 1.4.0 (November 2020) bersama dengan StateFlow. Menurut dokumentasi Kotlin Coroutines (2025), SharedFlow menggunakan penguncian granularitas halus untuk sinkronisasi pelanggan dan memberikan skalabilitas linier hingga 1000+ pelanggan simultan tanpa degradasi kinerja, yang dikonfirmasi oleh pengujian JetBrains.

SharedFlow vs StateFlow: kapan menggunakan apa

Pilihan antara SharedFlow dan StateFlow tergantung pada semantik data yang dikirimkan: status (StateFlow) atau event (SharedFlow). Di bawah ini adalah kriteria yang jelas dengan contoh.

KriteriaSharedFlowStateFlow
SemantikEvent satu kali (navigasi, toast, peringatan)Status UI (daftar, pemuatan, kesalahan)
Nilai awalTidak diperlukanWajib
Pengulangan saat berlanggananHanya jika replay > 0Selalu nilai terakhir
KonflasiTidak — event tidak hilang (jika buffer tidak penuh)Ya — hanya menyimpan yang terakhir
BufferingDapat dikonfigurasi melalui replay + extraBufferCapacityHanya 1 (replay=1 tetap)
PenggunaannavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

Aturan paling sederhana: jika data harus ditampilkan saat rotasi layar — ini status (StateFlow). Jika saat rotasi layar event tidak boleh berulang — ini event satu kali (SharedFlow). Misalnya, „toast dengan pesan kesalahan“ — SharedFlow: saat rotasi toast tidak boleh muncul lagi. „Daftar produk“ — StateFlow: saat rotasi daftar harus tetap di layar.

Di IT Sectr kami menggunakan SharedFlow untuk: perintah navigasi (pindah ke layar, membuka deep link), event UI (Snackbar, AlertDialog), notifikasi sistem (pembaruan data latar belakang, hasil pembayaran), event analitik (logging, pelacakan).

MutableSharedFlow: emit, tryEmit, dan buffering

MutableSharedFlow — versi SharedFlow yang dapat diubah dengan metode emit() (suspend) dan tryEmit() (non-suspend) untuk mengirim event. emit() akan menangguhkan jika buffer penuh dan onBufferOverflow = SUSPEND. tryEmit() mengembalikan Boolean — apakah event berhasil ditambahkan ke buffer.

kotlin
class EventBus {
    private val _events = MutableSharedFlow<UiEvent>(
        replay = 0,
        extraBufferCapacity = 10,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events: SharedFlow<UiEvent> get() = _events

    suspend fun sendEvent(event: UiEvent) {
        _events.emit(event)
    }

    fun trySendEvent(event: UiEvent): Boolean {
        return _events.tryEmit(event)
    }
}

sealed interface UiEvent {
    data class ShowSnackbar(val message: String) : UiEvent
    data class NavigateTo(val route: String) : UiEvent
    data class ShowDialog(val title: String, val message: String) : UiEvent
}

Parameter konstruktor sangat penting: replay = 0 memastikan event tidak berulang untuk pelanggan baru; extraBufferCapacity = 10 — buffer untuk pengiriman event cepat sebelum UI berlangganan; DROP_OLDEST — strategi saat overflow: event lama dibuang, yang baru disimpan. Menurut Kotlin Coroutines Performance (JetBrains, 2024), SharedFlow dengan extraBufferCapacity = 64 memproses lebih dari 100.000 event per detik tanpa kehilangan.

SharedFlow untuk event satu kali: pola Event

Pola Event (atau UiEvent) — cara yang direkomendasikan Google untuk mengirimkan event satu kali dari ViewModel ke View. Tidak seperti status (StateFlow), event harus diproses tepat satu kali, dan saat rotasi layar tidak boleh berulang. SharedFlow dengan replay = 0 sangat ideal untuk tugas ini.

kotlin
class CheckoutViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<CheckoutState>(CheckoutState.Idle)
    val uiState: StateFlow<CheckoutState> get() = _uiState

    private val _event = MutableSharedFlow<CheckoutEvent>()
    val event: SharedFlow<CheckoutEvent> get() = _event

    fun placeOrder() {
        viewModelScope.launch {
            _uiState.value = CheckoutState.Loading
            try {
                val orderId = orderRepository.createOrder(cart)
                _uiState.value = CheckoutState.Success(orderId)
                _event.emit(CheckoutEvent.NavigateToOrderTracking(orderId))
            } catch (e: Exception) {
                _uiState.value = CheckoutState.Error(e.message)
                _event.emit(CheckoutEvent.ShowErrorSnackbar(e.message ?: "Kesalahan format"))
            }
        }
    }
}

sealed interface CheckoutEvent {
    data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
    data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}

Di View (Activity/Fragment): langganan event harus dilakukan di lifecycleScope dengan repeatOnLifecycle(STATE.STARTED). Setiap kali masuk ke STARTED, langganan dibuat ulang, tetapi event tidak berulang karena SharedFlow dengan replay=0 telah melepaskannya. Ini memastikan bahwa navigasi ke layar pelacakan pesanan hanya terjadi satu kali, bukan setiap rotasi.

Parameter SharedFlow: replay, extraBufferCapacity, onBufferOverflow

Konstruktor MutableSharedFlow menerima tiga parameter yang menentukan perilaku buffer. Konfigurasi yang salah dapat menyebabkan hilangnya event atau pemblokiran emit().

ParameterTipeDefaultDeskripsi
replayInt0Jumlah event terakhir yang diputar ulang ke pelanggan baru. 0 = jangan putar ulang, 1 = seperti StateFlow
extraBufferCapacityInt0Buffer tambahan di luar replay. Event disimpan dalam buffer melingkar. 64 — batas yang direkomendasikan untuk sebagian besar skenario
onBufferOverflowBufferOverflowSUSPENDStrategi saat buffer penuh: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// Konfigurasi untuk berbagai skenario:

// 1. Event UI satu kali (navigasi, toast)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Aliran replay untuk sinkronisasi status (seperti StateFlow)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Pengiriman event frekuensi tinggi (analitik, log)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Penting: extraBufferCapacity + replay = ukuran total buffer. Jika emit() dipanggil lebih cepat daripada pelanggan memproses event, buffer akan penuh dan onBufferOverflow akan terpicu. Untuk event UI, DROP_OLDEST — strategi aman: event lama (navigasi yang sudah tidak relevan) dibuang demi yang baru. Untuk transaksi keuangan, gunakan SUSPEND — ini memastikan tidak ada event yang hilang dengan mengorbankan pemblokiran pengirim.

Contoh kode: SharedFlow di Kotlin

Contoh 1: SharedFlow untuk navigasi dengan Jetpack Navigation

Perintah navigasi — use case klasik untuk SharedFlow. Fragment berlangganan event dan menjalankan navigasi. Saat rotasi layar, perintah tidak berulang.

kotlin
// ViewModel
class AuthViewModel : ViewModel() {
    private val _navEvent = MutableSharedFlow<NavEvent>()
    val navEvent: SharedFlow<NavEvent> get() = _navEvent

    fun onLoginSuccess() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.HOME))
        }
    }

    fun onLogout() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.LOGIN))
        }
    }
}

sealed interface NavEvent {
    data class NavigateTo(val route: String) : NavEvent
    data class NavigateBack(val popUpTo: String? = null) : NavEvent
}

// Di Fragment:
viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.navEvent.collect { navEvent ->
            when (navEvent) {
                is NavEvent.NavigateTo -> findNavController().navigate(navEvent.route)
                is NavEvent.NavigateBack -> findNavController().popBackStack()
            }
        }
    }
}

Contoh 2: SharedFlow dengan Room dan operator Flow

Skenario kompleks: SharedFlow untuk notifikasi tentang event latar belakang dengan menggabungkan StateFlow untuk UI.

kotlin
class NotificationViewModel : ViewModel() {
    private val _toastMessage = MutableSharedFlow<String>()
    val toastMessage: SharedFlow<String> get() = _toastMessage

    private val _notifications = MutableStateFlow<List<Notification>>(emptyList())
    val notifications: StateFlow<List<Notification>> get() = _notifications

    init {
        viewModelScope.launch {
            notificationChannel
                .consumeAsFlow()
                .collect { notification ->
                    _notifications.value = _notifications.value + notification
                    _toastMessage.emit("Notifikasi baru: ${notification.title}")
                }
        }
    }

    fun dismissNotification(id: String) {
        _notifications.value = _notifications.value.filter { it.id != id }
    }

    fun markAllRead() {
        viewModelScope.launch {
            _notifications.value = _notifications.value.map { it.copy(isRead = true) }
            _toastMessage.emit("Semua notifikasi ditandai sebagai telah dibaca")
        }
    }
}

Dalam contoh ini: StateFlow menyimpan daftar notifikasi (status — dipertahankan saat rotasi), SharedFlow mengirim pesan toast (event satu kali — tidak berulang saat rotasi). Kombinasi dua tipe Flow — pola yang direkomendasikan Google untuk ViewModel sejak 2022.

Pertanyaan yang Sering Diajukan

Bisakah SharedFlow kehilangan event?

Ya, jika buffer penuh dan onBufferOverflow = DROP_OLDEST atau DROP_LATEST. SharedFlow tidak menjamin pengiriman setiap event — ini bukan antrian pesan (seperti Channel). Jika diperlukan pengiriman terjamin semua event, gunakan Channel dengan buffer tidak terbatas (UNLIMITED) atau BroadcastChannel (deprecated). Untuk event UI, hilangnya event usang (misalnya, navigasi lama) — adalah perilaku yang diharapkan, bukan bug.

Apa perbedaan SharedFlow dengan Channel?

Channel — antrian FIFO di mana setiap event dikirimkan ke tepat satu pelanggan (titik-ke-titik). SharedFlow — siaran: setiap event dikirimkan ke SEMUA pelanggan aktif. SharedFlow lebih dekat ke BroadcastChannel (yang sudah deprecated) dan cocok untuk skenario „satu-ke-banyak“. Channel — untuk „satu-ke-satu“ (pool thread, pipeline). Menurut rekomendasi JetBrains, SharedFlow adalah pengganti BroadcastChannel untuk semua proyek baru.

Bagaimana cara membuat SharedFlow thread-safe?

SharedFlow sudah thread-safe — emit() dan collect() tersinkronisasi dengan benar. Banyak thread dapat memanggil emit() tanpa pemblokiran, dan semua pelanggan aktif akan menerima event dalam urutan yang benar. tryEmit() tidak memblokir — mengembalikan false jika buffer penuh. Untuk sistem dengan beban tinggi, gunakan tryEmit() dengan DROP_OLDEST — ini mencegah pemblokiran thread.

Mengapa SharedFlow tidak digunakan untuk status?

SharedFlow tanpa replay=1 tidak menyimpan nilai terakhir — saat rotasi layar, pelanggan baru tidak menerima status saat ini, UI tetap kosong. Dengan replay=1, SharedFlow berperilaku seperti StateFlow, tetapi kehilangan optimasi perbandingan melalui equals(), yang menyebabkan notifikasi tidak perlu saat mengirim ulang nilai yang sama. StateFlow — pilihan tepat untuk status; SharedFlow — untuk event.

Bagaimana cara menguji SharedFlow?

Untuk menguji SharedFlow, gunakan Turbine — pustaka Kotlin untuk menguji Flow. Turbine memungkinkan Anda memeriksa setiap emisi secara terpisah dengan batas waktu dan verifikasi penyelesaian. Contoh: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Anda juga dapat menggunakan .toList() di runTest dengan menentukan jumlah event yang diharapkan.

Ringkasan

  • SharedFlow — aliran reaktif panas untuk event satu kali, tidak terikat pada status terakhir.
  • SharedFlow vs StateFlow: SharedFlow — event (navigasi, toast, peringatan), StateFlow — status (daftar, pemuatan, kesalahan).
  • MutableSharedFlow dengan replay=0, extraBufferCapacity=5, DROP_OLDEST — konfigurasi standar untuk event UI.
  • emit() — fungsi suspend untuk pengiriman memblokir; tryEmit() — non-suspend dengan hasil Boolean.
  • Pola UiEvent dengan sealed class — cara yang direkomendasikan untuk mengirimkan event satu kali dari ViewModel ke View.
  • SharedFlow menjamin pengiriman ke setiap pelanggan, tetapi tidak menjamin pengiriman setiap event saat buffer overflow.
  • Kombinasi SharedFlow + StateFlow dalam satu ViewModel — pola optimal untuk arsitektur yang memisahkan status dan event.

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