SharedFlow Nedir? Android'de SharedFlow vs StateFlow

Yazar: IT Sectr Yayınlanma: 2026-02-20 Okuma süresi: 9 dk

SharedFlow, Kotlin Coroutines kütüphanesinden, ekran döndürme veya abonenin yeniden oluşturulması sırasında tekrarlanmaması gereken tek seferlik olaylar (one-shot events) için optimize edilmiş sıcak bir reaktif akıştır. SharedFlow'un StateFlow'dan nasıl farklılaştığını gösteriyoruz: StateFlow'un aksine, SharedFlow yeni aboneler için son değeri saklamaz ve replay, extraBufferCapacity ve onBufferOverflow yapılandırmasını destekler. Google'a göre (Android Developers, 2025), SharedFlow, gezinme komutları, Snackbar mesajları ve tam olarak bir kez işlenmesi gereken diğer olaylar için önerilen çözümdür.

Önemli

  • SharedFlow — tek seferlik olaylar için hot flow: yeni aboneler replay ayarı olmadan önceki değerleri almaz.
  • MutableSharedFlow — olayları göndermek için emit() ve tryEmit() yöntemlerine sahip değiştirilebilir sürüm.
  • SharedFlow vs StateFlow: SharedFlow değerleri birleştirmez (birden çok değeri arabelleğe alabilir), başlangıç değeri gerektirmez, tek seferlik olaylar için uygundur.
  • replay — yeni abonelere oynatılan en son olayların sayısı (varsayılan 0).
  • extraBufferCapacity — emit()'in bloke edilmesini önlemek için replay dışında ek arabellek.

Kotlin'de SharedFlow Nedir?

SharedFlow, kotlinx.coroutines.flow kütüphanesinden, StateFlow'un aksine tek bir duruma bağlı olmayan ve isteğe bağlı sayıda olayı isteğe bağlı abonelere gönderebilen sıcak bir akıştır (hot flow). SharedFlow, StateFlow için temel türdür — StateFlow aslında replay = 1 ile SharedFlow üzerinden uygulanır.

SharedFlow'un temel özelliği, son değeri saklama zorunluluğunun olmamasıdır. Varsayılan olarak (replay = 0), yeni bir abone yeni bir olay gönderilene kadar hiçbir şey almaz. Bu, SharedFlow'u olayın tam olarak bir kez işlenmesi gereken senaryolar için ideal hale getirir: gezinme, Snackbar, sistem bildirimleri, QR kod tarama sonuçları.

SharedFlow, StateFlow ile birlikte kotlinx.coroutines 1.4.0'da (Kasım 2020) kararlı hale getirildi. Kotlin Coroutines belgelerine göre (2025), SharedFlow, aboneleri senkronize etmek için ince taneli kilit kullanır ve JetBrains testleriyle onaylandığı üzere performans düşüşü olmadan 1000+ eşzamanlı aboneye kadar doğrusal ölçeklenebilirlik sağlar.

SharedFlow vs StateFlow: Ne Zaman Hangisi Kullanılır

SharedFlow ve StateFlow arasındaki seçim, iletilen verilerin anlambilimine bağlıdır: durum (StateFlow) veya olay (SharedFlow). Aşağıda — örneklerle net kriterler.

KriterSharedFlowStateFlow
AnlambilimTek seferlik olaylar (gezinme, bildirim, uyarı)UI durumu (liste, yükleme, hata)
Başlangıç değeriGerekli değilZorunlu
Abonelikte tekrarSadece replay > 0 iseHer zaman son değer
BirleştirmeHayır — olaylar kaybolmaz (arabellek taşmazsa)Evet — sadece sonuncuyu saklar
Arabelleğe almareplay + extraBufferCapacity ile yapılandırılabilirSadece 1 (replay=1 sabit)
KullanımnavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

Basit kural: veriler ekran döndürüldüğünde gösterilmeliyse — bu durumdur (StateFlow). Ekran döndürüldüğünde olay tekrarlanmamalıysa — bu tek seferlik olaydır (SharedFlow). Örneğin, «hata mesajı bildirimi» — SharedFlow: ekran döndürüldüğünde bildirim tekrar gösterilmemelidir. «Ürün listesi» — StateFlow: ekran döndürüldüğünde liste ekranda kalmalıdır.

IT Sectr'de SharedFlow'u şunlar için kullanıyoruz: gezinme komutları (ekrana geçiş, derin bağlantı açma), UI olayları (Snackbar, AlertDialog), sistem bildirimleri (arka planda veri güncelleme, ödeme sonucu), analitik olaylar (günlükleme, izleme).

MutableSharedFlow: emit, tryEmit ve Arabelleğe Alma

MutableSharedFlow, olayları göndermek için emit() (suspend) ve tryEmit() (suspend olmayan) yöntemleriyle SharedFlow'un değiştirilebilir sürümüdür. Arabellek doluysa ve onBufferOverflow = SUSPEND ise emit() askıya alınır. tryEmit() Boolean döndürür — olayın arabelleğe başarıyla eklenip eklenmediğini belirtir.

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
}

Yapıcı parametreleri kritiktir: replay = 0, olayın yeni bir abone için tekrarlanmamasını garanti eder; extraBufferCapacity = 10, UI abone olmadan önce hızlı olay gönderimi durumunda arabellek sağlar; DROP_OLDEST, taşma stratejisidir: eski olaylar atılır, yenileri korunur. Kotlin Coroutines Performansına göre (JetBrains, 2024), extraBufferCapacity = 64 ile SharedFlow, kayıp olmadan saniyede 100.000'den fazla olayı işler.

SharedFlow ile Tek Seferlik Olaylar: Event Deseni

Event Deseni (veya UiEvent), Google tarafından ViewModel'den View'e tek seferlik olayları iletmek için önerilen yöntemdir. Durumdan (StateFlow) farklı olarak, olay tam olarak bir kez işlenmeli ve ekran döndürüldüğünde tekrarlanmamalıdır. replay = 0 ile SharedFlow bu görev için idealdir.

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 ?: "Sipariş hatası"))
            }
        }
    }
}

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

View'de (Activity/Fragment): olaya abonelik, lifecycleScope içinde repeatOnLifecycle(STATE.STARTED) ile yapılmalıdır. STARTED durumuna her girişte abonelik yeniden oluşturulur, ancak olay tekrarlanmaz çünkü replay=0 ile SharedFlow onu zaten serbest bırakmıştır. Bu, sipariş takip ekranına gezinmenin her ekran döndürmede değil, yalnızca bir kez gerçekleşmesini garanti eder.

SharedFlow Parametreleri: replay, extraBufferCapacity, onBufferOverflow

MutableSharedFlow yapıcısı, arabellek davranışını belirleyen üç parametre alır. Yanlış yapılandırma, olay kaybına veya emit()'in bloke olmasına neden olabilir.

ParametreTürVarsayılanAçıklama
replayInt0Yeni aboneye oynatılan en son olayların sayısı. 0 = oynatma, 1 = StateFlow gibi
extraBufferCapacityInt0Replay dışında ek arabellek. Olaylar dairesel arabellekte saklanır. 64 — çoğu senaryo için önerilen sınır
onBufferOverflowBufferOverflowSUSPENDArabellek dolduğunda strateji: SUSPEND, DROP_OLDEST, DROP_LATEST
// Farklı senaryolar için yapılandırmalar:

// 1. Tek seferlik UI olayları (gezinme, bildirimler)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Durum senkronizasyonu için Replay akışı (StateFlow gibi)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Yüksek frekanslı olay gönderimi (analitik, günlükler)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Önemli: extraBufferCapacity + replay = toplam arabellek boyutu. emit(), abonenin olayı işlemesinden daha hızlı çağrılırsa arabellek dolar ve onBufferOverflow devreye girer. UI olayları için DROP_OLDEST güvenli bir stratejidir: eski olaylar (artık güncel olmayan gezinmeler) yenileri lehine atılır. Finansal işlemler için SUSPEND kullanın — bu, göndereni bloke etme pahasına hiçbir olayın kaybolmamasını garanti eder.

Kotlin'de SharedFlow Kod Örnekleri

Örnek 1: Jetpack Navigation ile SharedFlow Kullanımı

Gezinme komutları, SharedFlow için klasik bir kullanım durumudur. Fragment olaylara abone olur ve gezinmeyi gerçekleştirir. Ekran döndürüldüğünde komut tekrarlanmaz.

// 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
}

// Fragment'te:
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()
            }
        }
    }
}

Örnek 2: Room ve Flow Operatörleriyle SharedFlow

Karmaşık senaryo: UI için StateFlow ile birleştirerek arka plan olay bildirimleri için SharedFlow.

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("Yeni bildirim: ${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("Tüm bildirimler okundu olarak işaretlendi")
        }
    }
}

Bu örnekte: StateFlow bildirim listesini saklar (durum — ekran döndürüldüğünde korunur), SharedFlow bildirim mesajlarını gönderir (tek seferlik olaylar — ekran döndürüldüğünde tekrarlanmaz). İki Flow türünün kombinasyonu, Google tarafından 2022'den itibaren ViewModel için önerilen desendir.

Sıkça Sorulan Sorular

SharedFlow bir olayı kaybedebilir mi?

Evet, arabellek dolarsa ve onBufferOverflow = DROP_OLDEST veya DROP_LATEST ise. SharedFlow her olayın teslimini garanti etmez — bu bir mesaj kuyruğu (Channel gibi) değildir. Tüm olayların garantili teslimi gerekiyorsa, taşmayan arabellekli (UNLIMITED) Channel veya BroadcastChannel (kullanımdan kaldırıldı) kullanın. UI olayları için eski olayların kaybı (örneğin, eski bir gezinme) beklenen davranıştır, hata değildir.

SharedFlow, Channel'dan nasıl farklıdır?

Channel, her olayın tam olarak bir aboneye iletildiği (noktadan noktaya) bir FIFO kuyruğudur. SharedFlow bir yayındır: her olay TÜM aktif abonelere iletilir. SharedFlow, BroadcastChannel'e (kullanımdan kaldırıldı) daha yakındır ve «bire-çok» senaryoları için uygundur. Channel, «bire-bir» içindir (iş parçacığı havuzları, pipeline). JetBrains'in önerisine göre SharedFlow, tüm yeni projeler için BroadcastChannel'in yerini alır.

SharedFlow nasıl iş parçacığı güvenli hale getirilir?

SharedFlow zaten iş parçacığı güvenlidir — emit() ve collect() doğru şekilde senkronize edilmiştir. Birden çok iş parçacığı kilitlenme olmadan emit() çağırabilir ve tüm aktif aboneler olayları doğru sırada alır. tryEmit() bloke edici değildir — arabellek doluysa false döndürür. Yüksek yük sistemleri için DROP_OLDEST ile tryEmit() kullanın — bu, iş parçacıklarının bloke olmasını önler.

SharedFlow neden durum için kullanılmaz?

replay=1 olmadan SharedFlow son değeri saklamaz — ekran döndürüldüğünde yeni abone geçerli durumu almaz, UI boş kalır. replay=1 ile SharedFlow, StateFlow gibi davranır ancak equals() karşılaştırma optimizasyonunu kaybeder, bu da aynı değerin tekrar gönderilmesinde gereksiz bildirimlere neden olur. StateFlow durum için doğru seçimdir; SharedFlow olaylar içindir.

SharedFlow nasıl test edilir?

SharedFlow'u test etmek için Turbine kullanın — Flow testi için Kotlin kütüphanesi. Turbine, her bir emisyonu zaman aşımları ve tamamlama kontrolü ile ayrı ayrı kontrol etmenizi sağlar. Örnek: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Ayrıca, beklenen olay sayısını belirterek runTest içinde .toList() kullanılabilir.

Özet

  • SharedFlow, son duruma bağlı olmayan tek seferlik olaylar için sıcak bir reaktif akıştır.
  • SharedFlow vs StateFlow: SharedFlow — olaylar (gezinme, bildirimler, uyarılar), StateFlow — durum (listeler, yükleme, hatalar).
  • MutableSharedFlow replay=0, extraBufferCapacity=5, DROP_OLDEST ile — UI olayları için standart yapılandırma.
  • emit() — bloke edici gönderme için suspend işlevi; tryEmit() — Boolean sonuçlu suspend olmayan işlev.
  • Sealed class ile UiEvent deseni — ViewModel'den View'e tek seferlik olayları iletmek için önerilen yöntem.
  • SharedFlow, her aboneye teslimi garanti eder, ancak arabellek taşması durumunda her olayın teslimini garanti etmez.
  • Tek bir ViewModel'de SharedFlow + StateFlow kombinasyonu, durumu ve olayları ayıran mimari için en uygun desendir.

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun