SharedFlow — Kotlin Coroutines kitabxanasından isti reaktiv axın, ekranın çevrilməsi və ya abunəçinin yenidən yaradılması zamanı təkrarlanmaması lazım olan birdəfəlik hadisələr (one-shot events) üçün optimallaşdırılıb. SharedFlow-un StateFlow-dan nə ilə fərqləndiyini göstəririk: StateFlow-dan fərqli olaraq, SharedFlow yeni abunəçilər üçün son dəyəri saxlamır və replay, extraBufferCapacity və onBufferOverflow konfiqurasiyasını dəstəkləyir. Google (Android Developers, 2025) məlumatına görə, SharedFlow naviqasiya əmrləri, Snackbar mesajları və dəqiq bir dəfə işlənməli olan digər hadisələr üçün tövsiyə olunan həll yoludur.
Əsas
SharedFlow — kotlinx.coroutines.flow kitabxanasından isti axın (hot flow) olub, StateFlow-dan fərqli olaraq, bir vəziyyətə bağlı deyil və ixtiyari sayda hadisəni ixtiyari abunəçilərə emit edə bilər. SharedFlow StateFlow üçün əsas tipdir — StateFlow replay = 1 ilə SharedFlow vasitəsilə reallaşdırılıb.
SharedFlow-un əsas xüsusiyyəti — son dəyəri saxlamaq məcburiyyətində deyil. Standart olaraq (replay = 0) yeni abunəçi yeni hadisə göndərilənə qədər heç nə almır. Bu, SharedFlow-u hadisənin dəqiq bir dəfə işlənməli olduğu ssenarilər üçün ideal edir: naviqasiya, Snackbar, sistem bildirişləri, QR kod skan nəticələri.
SharedFlow kotlinx.coroutines 1.4.0 (noyabr 2020) ilə birlikdə StateFlow ilə stabilləşdirilib. Kotlin Coroutines (2025) sənədlərinə görə, SharedFlow abunəçilərin sinxronizasiyası üçün incə dənəli bloklamadan istifadə edir və JetBrains testləri ilə təsdiqlənmiş performans deqradasiyası olmadan 1000+ eyni vaxtda abunəçiyə qədər xətti miqyaslana bilirlik təmin edir.
SharedFlow və StateFlow arasında seçim ötürülən məlumatların semantikasından asılıdır: vəziyyət (StateFlow) və ya hadisə (SharedFlow). Aşağıda nümunələrlə aydın meyarlar verilmişdir.
| Meyar | SharedFlow | StateFlow |
|---|---|---|
| Semantika | Birdəfəlik hadisələr (naviqasiya, toast, alert) | UI vəziyyəti (siyahı, yükləmə, xəta) |
| İlkin dəyər | Tələb olunmur | Məcburidir |
| Abunəlikdə təkrarlama | Yalnız replay > 0 olduqda | Həmişə son dəyər |
| Konflyasiya | Xeyr — hadisələr itmir (bufer dolmasa) | Bəli — yalnız sonuncunu saxlayır |
| Buferləşdirmə | replay + extraBufferCapacity ilə konfiqurasiya olunur | Yalnız 1 (replay=1 sabit) |
| İstifadə | navigationEvent, showSnackbar, openDialog | items, isLoading, uiState |
Ən sadə qayda: əgər məlumatlar ekran çevrildikdə göstərilməlidirsə — bu vəziyyətdir (StateFlow). Ekran çevrildikdə hadisə təkrarlanmamalıdırsa — bu birdəfəlik hadisədir (SharedFlow). Məsələn, „xəta mesajı ilə toast“ — SharedFlow: çevrilmə zamanı toast yenidən göstərilməməlidir. „Məhsul siyahısı“ — StateFlow: çevrilmə zamanı siyahı ekranda qalmalıdır.
IT Sectr-də SharedFlow-u istifadə edirik: naviqasiya əmrləri (ekrana keçid, dərin link açma), UI hadisələri (Snackbar, AlertDialog), sistem bildirişləri (fon məlumatlarının yenilənməsi, ödəniş nəticəsi), analitik hadisələr (loglama, izləmə).
MutableSharedFlow — hadisələri göndərmək üçün emit() (suspend) və tryEmit() (non-suspend) metodları ilə SharedFlow-un dəyişdirilə bilən versiyası. emit() bufer doludursa və onBufferOverflow = SUSPEND olduqda dayanır. tryEmit() Boolean qaytarır — hadisənin buferə uğurla əlavə edilib-edilmədiyini.
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
}
Konstruktor parametrləri kritik əhəmiyyətlidir: replay = 0 hadisənin yeni abunəçi üçün təkrarlanmayacağını təmin edir; extraBufferCapacity = 10 — UI abunə olana qədər sürətli hadisə göndərmə halı üçün bufer; DROP_OLDEST — daşma strategiyası: köhnə hadisələr atılır, yeniləri saxlanılır. Kotlin Coroutines Performance (JetBrains, 2024) məlumatına görə, extraBufferCapacity = 64 olan SharedFlow itkisiz olaraq saniyədə 100 000-dən çox hadisə emal edir.
Event (və ya UiEvent) nümunəsi — Google tərəfindən ViewModel-dən View-ə birdəfəlik hadisələrin ötürülməsi üçün tövsiyə olunan üsuldur. Vəziyyətdən (StateFlow) fərqli olaraq, hadisə dəqiq bir dəfə işlənməli və ekran çevrildikdə təkrarlanmamalıdır. replay = 0 olan SharedFlow bu tapşırıq üçün idealdır.
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 ?: "Formatlaşdırma xətası"))
}
}
}
}
sealed interface CheckoutEvent {
data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}
View-də (Activity/Fragment): event-ə abunəlik lifecycleScope-də repeatOnLifecycle(STATE.STARTED) ilə yerinə yetirilməlidir. STARTED-ə hər daxil olmada abunəlik yenidən yaradılır, lakin hadisə təkrarlanmır, çünki replay=0 olan SharedFlow onu artıq boşaltmışdır. Bu, sifariş izləmə ekranına naviqasiyanın hər çevrilmədə deyil, yalnız bir dəfə baş verməsini təmin edir.
MutableSharedFlow konstruktoru buferin davranışını təyin edən üç parametr qəbul edir. Yanlış konfiqurasiya hadisələrin itməsinə və ya emit()-in bloklanmasına səbəb ola bilər.
| Parametr | Tip | Standart | Təsvir |
|---|---|---|---|
| replay | Int | 0 | Yeni abunəçiyə oxudulan son hadisələrin sayı. 0 = oxutma, 1 = StateFlow kimi |
| extraBufferCapacity | Int | 0 | Replay-dan əlavə bufer. Hadisələr dairəvi buferdə saxlanılır. 64 — əksər ssenarilər üçün tövsiyə olunan limit |
| onBufferOverflow | BufferOverflow | SUSPEND | Bufer dolduqda strategiya: SUSPEND, DROP_OLDEST, DROP_LATEST |
// Müxtəlif ssenarilər üçün konfiqurasiyalar:
// 1. Birdəfəlik UI hadisələri (naviqasiya, toastlar)
val uiEvents = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 5,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 2. Vəziyyət sinxronizasiyası üçün replay axını (StateFlow kimi)
val stateLike = MutableSharedFlow<AppState>(
replay = 1,
extraBufferCapacity = 0
)
// 3. Yüksək tezlikli hadisə göndərmə (analitika, loglar)
val analytics = MutableSharedFlow<AnalyticsEvent>(
replay = 0,
extraBufferCapacity = 100,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
Vacib: extraBufferCapacity + replay = ümumi bufer ölçüsü. emit() abunəçinin hadisəni emal etməsindən daha sürətli çağırılırsa, bufer dolur və onBufferOverflow işə düşür. UI hadisələri üçün DROP_OLDEST — təhlükəsiz strategiya: köhnə hadisələr (artıq aktual olmayan naviqasiyalar) yeniləri lehinə atılır. Maliyyə əməliyyatları üçün SUSPEND istifadə edin — bu, göndərənin bloklanması hesabına heç bir hadisənin itirilməməsinə zəmanət verir.
Naviqasiya əmrləri — SharedFlow üçün klassik istifadə halıdır. Fragment hadisələrə abunə olur və naviqasiyanı yerinə yetirir. Ekran çevrildikdə əmr təkrarlanmır.
// 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-də:
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()
}
}
}
}
Mürəkkəb ssenari: UI üçün StateFlow ilə birləşdirərək fon hadisələri haqqında bildirişlər üçün 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 bildiriş: ${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("Bütün bildirişlər oxunmuş kimi qeyd edildi")
}
}
}
Bu nümunədə: StateFlow bildiriş siyahısını saxlayır (vəziyyət — çevrilmədə qorunur), SharedFlow toast mesajlarını emit edir (birdəfəlik hadisələr — çevrilmədə təkrarlanmır). İki Flow tipinin birləşməsi — 2022-ci ildən Google tərəfindən ViewModel üçün tövsiyə olunan nümunədir.
Tez-tez verilən suallar
Bəli, əgər bufer doludursa və onBufferOverflow = DROP_OLDEST və ya DROP_LATEST olarsa. SharedFlow hər hadisənin çatdırılmasına zəmanət vermir — bu mesaj növbəsi deyil (Channel kimi). Bütün hadisələrin zəmanətli çatdırılmasına ehtiyacınız varsa, daşmayan buferlə (UNLIMITED) Channel və ya BroadcastChannel (deprecated) istifadə edin. UI hadisələri üçün köhnəlmiş hadisələrin itirilməsi (məsələn, köhnə naviqasiya) — gözlənilən davranışdır, səhv deyil.
Channel — FIFO növbəsi, burada hər hadisə dəqiq bir abunəçiyə çatdırılır (nöqtə-nöqtə). SharedFlow — yayım: hər hadisə BÜTÜN aktiv abunəçilərə çatdırılır. SharedFlow BroadcastChannel-ə (deprecated) daha yaxındır və „birdən-çoxa“ ssenariləri üçün uyğundur. Channel — „birdən-birə“ üçün (thread hovuzları, pipeline). JetBrains tövsiyəsinə görə, SharedFlow bütün yeni layihələr üçün BroadcastChannel-in əvəzidir.
SharedFlow artıq thread-safe-dir — emit() və collect() düzgün sinxronizasiya olunub. Çoxlu thread-lar bloklamalar olmadan emit() çağıra bilər və bütün aktiv abunəçilər hadisələri düzgün ardıcıllıqla alır. tryEmit() bloklamayan — bufer doludursa false qaytarır. Yüksək yüklü sistemlər üçün DROP_OLDEST ilə tryEmit() istifadə edin — bu, thread-ların bloklanmasının qarşısını alır.
replay=1 olmadan SharedFlow son dəyəri saxlamır — ekran çevrildikdə yeni abunəçi cari vəziyyəti almır, UI boş qalır. replay=1 ilə SharedFlow StateFlow kimi davranır, lakin equals() vasitəsilə müqayisə optimallaşdırmasını itirir, bu da eyni dəyərin təkrar göndərilməsi zamanı lazımsız bildirişlərə səbəb olur. StateFlow — vəziyyət üçün düzgün seçimdir; SharedFlow — hadisələr üçün.
SharedFlow-u test etmək üçün Turbine — Flow-u test etmək üçün Kotlin kitabxanasından istifadə edin. Turbine hər emissiyanı ayrıca, timeoutlar və tamamlama yoxlaması ilə yoxlamağa imkan verir. Nümunə: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Həmçinin runTest-də gözlənilən hadisələrin sayını göstərərək .toList() istifadə etmək olar.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun