SharedFlow — Kotlin Coroutines kutubxonasidan issiq reaktiv oqim, ekranning aylanishi yoki obunachining qayta yaratilishida takrorlanmasligi kerak bo'lgan bir martalik hodisalar (one-shot events) uchun optimallashtirilgan. SharedFlow StateFlow-dan nimasi bilan farq qilishini ko'rsatamiz: StateFlow-dan farqli o'laroq, SharedFlow yangi obunachilar uchun oxirgi qiymatni saqlamaydi va replay, extraBufferCapacity hamda onBufferOverflow konfiguratsiyasini qo'llab-quvvatlaydi. Google (Android Developers, 2025) ma'lumotlariga ko'ra, SharedFlow navigatsiya buyruqlari, Snackbar xabarlari va aynan bir marta qayta ishlanishi kerak bo'lgan boshqa hodisalar uchun tavsiya etilgan yechimdir.
Asosiy
SharedFlow — kotlinx.coroutines.flow kutubxonasidan issiq oqim (hot flow) bo'lib, StateFlow-dan farqli o'laroq, bitta holatga bog'liq emas va istalgan miqdordagi hodisalarni istalgan obunachilarga emit qilishi mumkin. SharedFlow StateFlow uchun asosiy tur hisoblanadi — StateFlow replay = 1 bilan SharedFlow orqali amalga oshirilgan.
SharedFlow-ning asosiy xususiyati — oxirgi qiymatni saqlash shart emas. Standart holatda (replay = 0) yangi obunachi yangi hodisa yuborilgunga qadar hech narsa olmaydi. Bu SharedFlow-ni hodisa aynan bir marta qayta ishlanishi kerak bo'lgan stsenariylar uchun ideal qiladi: navigatsiya, Snackbar, tizim bildirishnomalari, QR kod skanerlash natijalari.
SharedFlow kotlinx.coroutines 1.4.0 (noyabr 2020) da StateFlow bilan birga barqarorlashtirilgan. Kotlin Coroutines (2025) hujjatlariga ko'ra, SharedFlow obunachilarni sinxronlash uchun nozik donali blokirovkadan foydalanadi va JetBrains testlari bilan tasdiqlangan ishlash degradatsiyasisiz 1000+ bir vaqtning o'zidagi obunachilargacha chiziqli masshtablanishni ta'minlaydi.
SharedFlow va StateFlow o'rtasidagi tanlov uzatiladigan ma'lumotlarning semantikasiga bog'liq: holat (StateFlow) yoki hodisa (SharedFlow). Quyida misollar bilan aniq mezonlar keltirilgan.
| Mezon | SharedFlow | StateFlow |
|---|---|---|
| Semantika | Bir martalik hodisalar (navigatsiya, toast, ogohlantirish) | UI holati (ro'yxat, yuklash, xato) |
| Boshlang'ich qiymat | Talab qilinmaydi | Majburiy |
| Obunada takrorlash | Faqat replay > 0 bo'lsa | Har doim oxirgi qiymat |
| Konflyatsiya | Yo'q — hodisalar yo'qolmaydi (bufer to'lmasa) | Ha — faqat oxirgisini saqlaydi |
| Buferlash | replay + extraBufferCapacity orqali sozlanadi | Faqat 1 (replay=1 qat'iy) |
| Foydalanish | navigationEvent, showSnackbar, openDialog | items, isLoading, uiState |
Eng oddiy qoida: agar ma'lumotlar ekran aylanganda ko'rsatilishi kerak bo'lsa — bu holat (StateFlow). Agar ekran aylanganda hodisa takrorlanmasligi kerak bo'lsa — bu bir martalik hodisa (SharedFlow). Masalan, „xato xabari bilan toast“ — SharedFlow: aylanishda toast qayta ko'rsatilmasligi kerak. „Mahsulotlar ro'yxati“ — StateFlow: aylanishda ro'yxat ekranda qolishi kerak.
IT Sectr da SharedFlow-ni ishlatamiz: navigatsiya buyruqlari (ekranga o'tish, deep link ochish), UI hodisalari (Snackbar, AlertDialog), tizim bildirishnomalari (fondagi ma'lumotlarni yangilash, to'lov natijasi), analitik hodisalar (loglash, kuzatish).
MutableSharedFlow — hodisalarni yuborish uchun emit() (suspend) va tryEmit() (non-suspend) metodlari bilan SharedFlow-ning o'zgartiriladigan versiyasi. emit() bufer to'la bo'lsa va onBufferOverflow = SUSPEND bo'lsa to'xtatiladi. tryEmit() Boolean qaytaradi — hodisa buferga muvaffaqiyatli qo'shilganmi.
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 parametrlari juda muhim: replay = 0 hodisaning yangi obunachi uchun takrorlanmasligini kafolatlaydi; extraBufferCapacity = 10 — UI obuna bo'lgunga qadar tez hodisa yuborish uchun bufer; DROP_OLDEST — to'lib toshish strategiyasi: eski hodisalar tashlanadi, yangilari saqlanadi. Kotlin Coroutines Performance (JetBrains, 2024) ma'lumotlariga ko'ra, extraBufferCapacity = 64 bo'lgan SharedFlow yo'qotishlarsiz soniyada 100 000 dan ortiq hodisani qayta ishlaydi.
Event (yoki UiEvent) namunasi — Google tomonidan ViewModel-dan View-ga bir martalik hodisalarni uzatish uchun tavsiya etilgan usul. Holatdan (StateFlow) farqli o'laroq, hodisa aynan bir marta qayta ishlanishi va ekran aylanganda takrorlanmasligi kerak. replay = 0 bo'lgan SharedFlow bu vazifa uchun 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 ?: "Formatlash xatosi"))
}
}
}
}
sealed interface CheckoutEvent {
data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}
View-da (Activity/Fragment): hodisaga obuna lifecycleScope da repeatOnLifecycle(STATE.STARTED) bilan bajarilishi kerak. STARTED ga har bir kirishda obuna qayta yaratiladi, ammo hodisa takrorlanmaydi, chunki replay=0 bo'lgan SharedFlow uni allaqachon bo'shatgan. Bu buyurtmani kuzatish ekraniga navigatsiya har bir aylanishda emas, faqat bir marta sodir bo'lishini kafolatlaydi.
MutableSharedFlow konstruktori buferning xatti-harakatini belgilaydigan uchta parametrni qabul qiladi. Noto'g'ri sozlash hodisalarning yo'qolishiga yoki emit() bloklanishiga olib kelishi mumkin.
| Parametr | Tur | Standart | Tavsif |
|---|---|---|---|
| replay | Int | 0 | Yangi obunachiga takrorlanadigan oxirgi hodisalar soni. 0 = takrorlanmaydi, 1 = StateFlow kabi |
| extraBufferCapacity | Int | 0 | Replay-dan tashqari qo'shimcha bufer. Hodisalar halqali buferda saqlanadi. 64 — ko'p stsenariylar uchun tavsiya etilgan chegara |
| onBufferOverflow | BufferOverflow | SUSPEND | Bufer to'lganda strategiya: SUSPEND, DROP_OLDEST, DROP_LATEST |
// Turli stsenariylar uchun konfiguratsiyalar:
// 1. Bir martalik UI hodisalari (navigatsiya, toastlar)
val uiEvents = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 5,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 2. Holat sinxronizatsiyasi uchun replay oqimi (StateFlow kabi)
val stateLike = MutableSharedFlow<AppState>(
replay = 1,
extraBufferCapacity = 0
)
// 3. Yuqori chastotali hodisa yuborish (analitika, loglar)
val analytics = MutableSharedFlow<AnalyticsEvent>(
replay = 0,
extraBufferCapacity = 100,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
Muhim: extraBufferCapacity + replay = umumiy bufer hajmi. Agar emit() obunachining hodisani qayta ishlashidan tezroq chaqirilsa, bufer to'ladi va onBufferOverflow ishga tushadi. UI hodisalari uchun DROP_OLDEST — xavfsiz strategiya: eski hodisalar (endi dolzarb bo'lmagan navigatsiyalar) yangilari foydasiga tashlanadi. Moliyaviy tranzaktsiyalar uchun SUSPEND dan foydalaning — bu jo'natuvchini bloklash evaziga hech qanday hodisa yo'qolmasligini kafolatlaydi.
Navigatsiya buyruqlari — SharedFlow uchun klassik foydalanish holati. Fragment hodisalarga obuna bo'ladi va navigatsiyani amalga oshiradi. Ekran aylanganda buyruq takrorlanmaydi.
// 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-da:
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()
}
}
}
}
Murakkab stsenariy: UI uchun StateFlow bilan birlashtirgan holda fon hodisalari haqida bildirishnomalar uchun 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("Yangi bildirishnoma: ${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("Barcha bildirishnomalar o'qilgan deb belgilandi")
}
}
}
Ushbu misolda: StateFlow bildirishnomalar ro'yxatini saqlaydi (holat — aylanishda saqlanadi), SharedFlow toast xabarlarini emit qiladi (bir martalik hodisalar — aylanishda takrorlanmaydi). Ikki Flow turining kombinatsiyasi — 2022 yildan boshlab Google tomonidan ViewModel uchun tavsiya etilgan namunadir.
Tez-tez beriladigan savollar
Ha, agar bufer to'la bo'lsa va onBufferOverflow = DROP_OLDEST yoki DROP_LATEST bo'lsa. SharedFlow har bir hodisaning yetkazilishini kafolatlamaydi — bu xabarlar navbati emas (Channel kabi). Agar barcha hodisalarning kafolatlangan yetkazilishi kerak bo'lsa, to'lib toshmaydigan buferli (UNLIMITED) Channel yoki BroadcastChannel (deprecated) dan foydalaning. UI hodisalari uchun eskirgan hodisalarning yo'qolishi (masalan, eski navigatsiya) — kutilgan xatti-harakat, xato emas.
Channel — FIFO navbat, unda har bir hodisa aynan bitta obunachiga yetkaziladi (nuqta-nuqta). SharedFlow — translyatsiya: har bir hodisa BARCHA faol obunachilarga yetkaziladi. SharedFlow BroadcastChannel-ga (deprecated) yaqinroq va „birdan-ko'pga“ stsenariylari uchun mos keladi. Channel — „birdan-birga“ uchun (thread hovuzlari, pipeline). JetBrains tavsiyasiga ko'ra, SharedFlow barcha yangi loyihalar uchun BroadcastChannel o'rnini bosuvchi hisoblanadi.
SharedFlow allaqachon thread-safe — emit() va collect() to'g'ri sinxronlashtirilgan. Bir nechta thread-lar blokirovkalarsiz emit() chaqirishi mumkin va barcha faol obunachilar hodisalarni to'g'ri tartibda oladi. tryEmit() bloklamaydigan — bufer to'la bo'lsa false qaytaradi. Yuqori yuklangan tizimlar uchun DROP_OLDEST bilan tryEmit() dan foydalaning — bu thread-larning bloklanishining oldini oladi.
replay=1 bo'lmagan SharedFlow oxirgi qiymatni saqlamaydi — ekran aylanganda yangi obunachi joriy holatni olmaydi, UI bo'sh qoladi. replay=1 bilan SharedFlow StateFlow kabi harakat qiladi, lekin equals() orqali taqqoslash optimallashtirishini yo'qotadi, bu esa bir xil qiymatni qayta yuborishda keraksiz bildirishnomalarga sabab bo'ladi. StateFlow — holat uchun to'g'ri tanlov; SharedFlow — hodisalar uchun.
SharedFlow-ni test qilish uchun Turbine — Flow-ni test qilish uchun Kotlin kutubxonasidan foydalaning. Turbine har bir emissiyani alohida, taym-autlar va tugash tekshiruvi bilan tekshirish imkonini beradi. Misol: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Shuningdek, runTest da kutilgan hodisalar sonini ko'rsatib .toList() dan foydalanish mumkin.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.