SharedFlow: ano ito, SharedFlow vs StateFlow sa Android

May-akda: IT Sectr Nai-publish: 2026-02-20 Oras ng pagbabasa: 9 min

SharedFlow — isang mainit na reaktibong daloy mula sa library ng Kotlin Coroutines, na-optimize para sa isang beses na mga event (one-shot events) na hindi dapat maulit sa pag-ikot ng screen o muling paggawa ng subscriber. Ipinapakita namin ang pagkakaiba ng SharedFlow sa StateFlow: hindi tulad ng StateFlow, ang SharedFlow ay hindi nag-iimbak ng huling halaga para sa mga bagong subscriber at sumusuporta sa configuration ng replay, extraBufferCapacity at onBufferOverflow. Ayon sa Google (Android Developers, 2025), ang SharedFlow ay ang inirerekomendang solusyon para sa mga navigation command, Snackbar message at iba pang mga event na dapat iproseso nang eksaktong isang beses.

Mga Pangunahing Punto

  • SharedFlow — hot flow para sa isang beses na mga event: hindi natatanggap ng mga bagong subscriber ang mga nakaraang halaga nang walang setting ng replay.
  • MutableSharedFlow — nababagong bersyon na may mga pamamaraang emit() at tryEmit() para sa pagpapadala ng mga event.
  • SharedFlow vs StateFlow: Hindi kino-conflate ng SharedFlow ang mga halaga (maaaring mag-buffer ng marami), hindi nangangailangan ng paunang halaga, angkop para sa isang beses na mga event.
  • replay — bilang ng mga huling event na nire-play para sa mga bagong subscriber (default 0).
  • extraBufferCapacity — karagdagang buffer para sa mga event na lampas sa replay, pumipigil sa pag-block ng emit().

Ano ang SharedFlow sa Kotlin?

SharedFlow — ay isang mainit na daloy (hot flow) mula sa library ng kotlinx.coroutines.flow na, hindi tulad ng StateFlow, ay hindi nakatali sa isang estado at maaaring mag-emit ng anumang bilang ng mga event sa anumang mga subscriber. Ang SharedFlow ay ang base type para sa StateFlow — ang StateFlow ay ipinatupad sa pamamagitan ng SharedFlow na may replay = 1.

Ang pangunahing katangian ng SharedFlow — hindi nito kailangang iimbak ang huling halaga. Bilang default (replay = 0), ang isang bagong subscriber ay hindi tumatanggap ng anuman hanggang sa maipadala ang isang bagong event. Ginagawa nitong perpekto ang SharedFlow para sa mga sitwasyon kung saan ang event ay dapat iproseso nang eksaktong isang beses: navigation, Snackbar, mga notification ng system, mga resulta ng pag-scan ng QR code.

Ang SharedFlow ay na-stabilize sa kotlinx.coroutines 1.4.0 (Nobyembre 2020) kasama ng StateFlow. Ayon sa dokumentasyon ng Kotlin Coroutines (2025), ang SharedFlow ay gumagamit ng fine-grained locking para sa pag-sync ng mga subscriber at nagbibigay ng linear scalability hanggang 1000+ sabay-sabay na subscriber nang walang pagbaba ng performance, na kinumpirma ng mga pagsubok ng JetBrains.

SharedFlow vs StateFlow: kailan gagamitin ang alin

Ang pagpili sa pagitan ng SharedFlow at StateFlow ay depende sa semantika ng ipinapadalang data: estado (StateFlow) o event (SharedFlow). Nasa ibaba ang malinaw na mga pamantayan na may mga halimbawa.

PamantayanSharedFlowStateFlow
SemantikaIsang beses na mga event (navigation, toast, alert)Estado ng UI (listahan, pag-load, error)
Paunang halagaHindi kinakailanganKinakailangan
Pag-uulit sa subscriptionLamang kung replay > 0Palaging huling halaga
ConflationHindi — hindi nawawala ang mga event (kung hindi puno ang buffer)Oo — iniimbak lamang ang huli
BufferingNako-configure sa pamamagitan ng replay + extraBufferCapacityLamang 1 (replay=1 fixed)
PaggamitnavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

Ang pinakasimpleng patakaran: kung ang data ay dapat ipakita sa pag-ikot ng screen — ito ay estado (StateFlow). Kung sa pag-ikot ng screen ang event ay hindi dapat maulit — ito ay isang beses na event (SharedFlow). Halimbawa, „toast na may mensahe ng error“ — SharedFlow: sa pag-ikot ay hindi dapat lumitaw muli ang toast. „Listahan ng mga produkto“ — StateFlow: sa pag-ikot ang listahan ay dapat manatili sa screen.

Sa IT Sectr ginagamit namin ang SharedFlow para sa: mga navigation command (paglipat sa screen, pagbubukas ng deep link), mga event ng UI (Snackbar, AlertDialog), mga notification ng system (pag-update ng data sa background, resulta ng pagbabayad), mga event ng analytics (pag-log, pag-track).

MutableSharedFlow: emit, tryEmit at buffering

MutableSharedFlow — nababagong bersyon ng SharedFlow na may mga pamamaraang emit() (suspend) at tryEmit() (non-suspend) para sa pagpapadala ng mga event. Ang emit() ay nase-suspend kung puno ang buffer at onBufferOverflow = SUSPEND. Ang tryEmit() ay nagbabalik ng Boolean — kung ang event ay matagumpay na naidagdag sa 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
}

Ang mga parameter ng constructor ay kritikal: replay = 0 ay ginagarantiyang hindi mauulit ang event para sa isang bagong subscriber; extraBufferCapacity = 10 — buffer para sa mabilisang pagpapadala ng mga event bago mag-subscribe ang UI; DROP_OLDEST — strategy sa overflow: ang mga lumang event ay itinatapon, ang mga bago ay iniimbak. Ayon sa Kotlin Coroutines Performance (JetBrains, 2024), ang SharedFlow na may extraBufferCapacity = 64 ay nagpoproseso ng higit sa 100,000 event bawat segundo nang walang pagkawala.

SharedFlow para sa isang beses na mga event: ang pattern ng Event

Ang pattern ng Event (o UiEvent) — ang inirerekomendang paraan ng Google para sa pagpapadala ng isang beses na mga event mula ViewModel papunta sa View. Hindi tulad ng estado (StateFlow), ang event ay dapat iproseso nang eksaktong isang beses, at sa pag-ikot ng screen ay hindi dapat maulit. Ang SharedFlow na may replay = 0 ay perpekto para sa gawaing ito.

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 ?: "Error sa pag-format"))
            }
        }
    }
}

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

Sa View (Activity/Fragment): ang subscription sa event ay dapat isagawa sa lifecycleScope na may repeatOnLifecycle(STATE.STARTED). Sa bawat pagpasok sa STARTED, ang subscription ay muling ginagawa, ngunit ang event ay hindi na uulit dahil ang SharedFlow na may replay=0 ay nailabas na ito. Ginagarantiyang nito na ang navigation sa screen ng pagsubaybay ng order ay magaganap nang isang beses lamang, hindi sa bawat pag-ikot.

Mga parameter ng SharedFlow: replay, extraBufferCapacity, onBufferOverflow

Ang constructor ng MutableSharedFlow ay tumatanggap ng tatlong parameter na nagtatakda ng pag-uugali ng buffer. Ang maling configuration ay maaaring humantong sa pagkawala ng mga event o pag-block ng emit().

ParameterUriDefaultPaglalarawan
replayInt0Bilang ng mga huling event na nire-play para sa bagong subscriber. 0 = huwag i-replay, 1 = tulad ng StateFlow
extraBufferCapacityInt0Karagdagang buffer lampas sa replay. Ang mga event ay iniimbak sa isang ring buffer. 64 — inirerekomendang limit para sa karamihan ng mga sitwasyon
onBufferOverflowBufferOverflowSUSPENDStrategy kapag puno ang buffer: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// Mga configuration para sa iba't ibang sitwasyon:

// 1. Isang beses na UI event (navigation, toast)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Replay flow para sa pag-sync ng estado (tulad ng StateFlow)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Mataas na dalas ng pagpapadala ng event (analytics, log)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Mahalaga: extraBufferCapacity + replay = kabuuang sukat ng buffer. Kung ang emit() ay tinatawag nang mas mabilis kaysa sa pagproseso ng subscriber ng event, mapupuno ang buffer at ma-t-trigger ang onBufferOverflow. Para sa mga event ng UI, ang DROP_OLDEST — ligtas na strategy: ang mga lumang event (mga navigation na hindi na nauugnay) ay itinatapon pabor sa mga bago. Para sa mga financial transaction, gamitin ang SUSPEND — ginagarantiyang nito na walang event ang mawawala sa halaga ng pag-block ng nagpadala.

Mga halimbawa ng code: SharedFlow sa Kotlin

Halimbawa 1: SharedFlow para sa navigation gamit ang Jetpack Navigation

Mga navigation command — klasikong use case para sa SharedFlow. Nag-subscribe ang Fragment sa mga event at nagsasagawa ng navigation. Sa pag-ikot ng screen, hindi nauulit ang command.

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
}

// Sa 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()
            }
        }
    }
}

Halimbawa 2: SharedFlow na may Room at Flow operators

Komplikadong sitwasyon: SharedFlow para sa mga notification tungkol sa background event na pinagsama sa StateFlow para sa 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("Bagong notification: ${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("Lahat ng notification ay minarkahan bilang nabasa")
        }
    }
}

Sa halimbawang ito: Iniimbak ng StateFlow ang listahan ng mga notification (estado — nananatili sa pag-ikot), nag-e-emit ang SharedFlow ng mga toast message (isang beses na mga event — hindi nauulit sa pag-ikot). Ang kombinasyon ng dalawang uri ng Flow — ang inirerekomendang pattern ng Google para sa ViewModel mula noong 2022.

Mga Madalas Itanong

Maaari bang mawalan ng event ang SharedFlow?

Oo, kung puno ang buffer at onBufferOverflow = DROP_OLDEST o DROP_LATEST. Hindi ginagarantiyahan ng SharedFlow ang paghahatid ng bawat event — hindi ito isang message queue (tulad ng Channel). Kung kailangan ang garantisadong paghahatid ng lahat ng event, gamitin ang Channel na may hindi umaapaw na buffer (UNLIMITED) o BroadcastChannel (deprecated). Para sa mga event ng UI, ang pagkawala ng mga lumang event (hal., lumang navigation) — ay inaasahang pag-uugali, hindi bug.

Paano naiiba ang SharedFlow sa Channel?

Channel — isang FIFO queue kung saan ang bawat event ay inihahatid sa eksaktong isang subscriber (point-to-point). SharedFlow — broadcast: ang bawat event ay inihahatid sa LAHAT ng aktibong subscriber. Ang SharedFlow ay mas malapit sa BroadcastChannel (na deprecated) at angkop para sa mga sitwasyong „isa-sa-marami“. Channel — para sa „isa-sa-isa“ (thread pools, pipeline). Ayon sa rekomendasyon ng JetBrains, ang SharedFlow ay kapalit ng BroadcastChannel para sa lahat ng bagong proyekto.

Paano gawing thread-safe ang SharedFlow?

Ang SharedFlow ay thread-safe na — ang emit() at collect() ay wastong naka-sync. Maraming thread ang maaaring tumawag ng emit() nang walang pag-block, at lahat ng aktibong subscriber ay makakatanggap ng mga event sa tamang pagkakasunod-sunod. Ang tryEmit() ay hindi bumabara — nagbabalik ng false kung puno ang buffer. Para sa mga system na may mataas na load, gamitin ang tryEmit() na may DROP_OLDEST — pinipigilan nito ang pag-block ng mga thread.

Bakit hindi ginagamit ang SharedFlow para sa estado?

Ang SharedFlow na walang replay=1 ay hindi nag-iimbak ng huling halaga — sa pag-ikot ng screen, ang bagong subscriber ay hindi makakatanggap ng kasalukuyang estado, mananatiling walang laman ang UI. Sa replay=1, ang SharedFlow ay kumikilos tulad ng StateFlow, ngunit nawawala ang optimization ng paghahambing sa pamamagitan ng equals(), na nagdudulot ng mga hindi kinakailangang notification sa muling pagpapadala ng parehong halaga. StateFlow — tamang pagpili para sa estado; SharedFlow — para sa mga event.

Paano i-test ang SharedFlow?

Para sa pag-test ng SharedFlow, gamitin ang Turbine — ang Kotlin library para sa pag-test ng Flow. Binibigyang-daan ka ng Turbine na suriin ang bawat emisyon nang hiwalay na may mga timeout at pag-verify ng pagkumpleto. Halimbawa: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Maaari mo ring gamitin ang .toList() sa runTest na may pagtukoy ng bilang ng inaasahang event.

Buod

  • SharedFlow — mainit na reaktibong daloy para sa isang beses na mga event, hindi nakatali sa huling estado.
  • SharedFlow vs StateFlow: SharedFlow — mga event (navigation, toast, alert), StateFlow — estado (mga listahan, pag-load, mga error).
  • MutableSharedFlow na may replay=0, extraBufferCapacity=5, DROP_OLDEST — karaniwang configuration para sa mga event ng UI.
  • emit() — suspend function para sa pag-block ng pagpapadala; tryEmit() — non-suspend na may Boolean na resulta.
  • Ang pattern na UiEvent na may sealed class — inirerekomendang paraan para sa pagpapadala ng isang beses na mga event mula ViewModel papunta sa View.
  • SharedFlow ginagarantiyahan ang paghahatid sa bawat subscriber, ngunit hindi ginagarantiyahan ang paghahatid ng bawat event sa buffer overflow.
  • Ang kombinasyon ng SharedFlow + StateFlow sa isang ViewModel — pinakamainam na pattern para sa arkitektura na naghihiwalay ng estado at mga event.

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din