SharedFlow Kotlin Coroutines لائبریری کا ایک گرم رد عمل کا بہاؤ ہے، جو ایک بار استعمال ہونے والے واقعات (one-shot events) کے لیے موزوں ہے جو اسکرین گھمانے یا سبسکرائبر کے دوبارہ بننے پر دہرائے نہیں جائیں۔ دکھا رہے ہیں کہ SharedFlow StateFlow سے کیسے مختلف ہے: StateFlow کے برعکس، SharedFlow نئے سبسکرائبرز کے لیے آخری قدر محفوظ نہیں کرتا اور replay، extraBufferCapacity اور onBufferOverflow کنفیگریشن کو سپورٹ کرتا ہے۔ Google (Android Developers, 2025) کے مطابق، SharedFlow نیویگیشن کمانڈز، Snackbar پیغامات اور دیگر واقعات کے لیے تجویز کردہ حل ہے جنہیں صرف ایک بار پروسیس کیا جانا چاہیے۔
اہم نکات
SharedFlow kotlinx.coroutines.flow لائبریری کا ایک گرم بہاؤ (hot flow) ہے، جو StateFlow کے برعکس کسی ایک حالت سے منسلک نہیں ہوتا اور من مانی تعداد میں واقعات من مانی سبسکرائبرز کو بھیج سکتا ہے۔ SharedFlow StateFlow کے لیے بنیادی قسم ہے — StateFlow دراصل replay = 1 کے ساتھ SharedFlow کے ذریعے لاگو کیا گیا ہے۔
SharedFlow کی کلیدی خصوصیت یہ ہے کہ اسے آخری قدر محفوظ کرنے کی ضرورت نہیں ہے۔ پہلے سے طے شدہ طور پر (replay = 0)، نیا سبسکرائبر کچھ حاصل نہیں کرتا جب تک کوئی نیا واقعہ نہ بھیجا جائے۔ یہ SharedFlow کو ان منظرناموں کے لیے مثالی بناتا ہے جہاں واقعہ کو صرف ایک بار پروسیس کیا جانا چاہیے: نیویگیشن، Snackbar، سسٹم نوٹیفیکیشن، QR کوڈ اسکیننگ کے نتائج۔
SharedFlow کو kotlinx.coroutines 1.4.0 (نومبر 2020) میں StateFlow کے ساتھ مستحکم کیا گیا تھا۔ Kotlin Coroutines دستاویزات (2025) کے مطابق، SharedFlow سبسکرائبرز کو ہم آہنگ کرنے کے لیے باریک دانے دار لاک کا استعمال کرتا ہے اور JetBrains ٹیسٹوں سے تصدیق شدہ کارکردگی میں تنزلی کے بغیر 1000+ بیک وقت سبسکرائبرز تک لکیری اسکیل ایبلٹی فراہم کرتا ہے۔
SharedFlow اور StateFlow کے درمیان انتخاب منتقل کردہ ڈیٹا کے معنویات پر منحصر ہے: حالت (StateFlow) یا واقعہ (SharedFlow)۔ ذیل میں — مثالوں کے ساتھ واضح معیار۔
| معیار | SharedFlow | StateFlow |
|---|---|---|
| معنویات | ایک بار استعمال ہونے والے واقعات (نیویگیشن، ٹوسٹ، الرٹ) | UI حالت (فہرست، لوڈنگ، خرابی) |
| ابتدائی قدر | ضروری نہیں | لازمی |
| سبسکرپشن پر دہرانا | صرف اگر replay > 0 ہو | ہمیشہ آخری قدر |
| یکجا کرنا | نہیں — واقعات ضائع نہیں ہوتے (اگر بفر بھر نہ جائے) | ہاں — صرف آخری کو محفوظ کرتا ہے |
| بفرنگ | replay + extraBufferCapacity کے ذریعے ترتیب پذیر | صرف 1 (replay=1 مقرر) |
| استعمال | navigationEvent, showSnackbar, openDialog | items, isLoading, uiState |
سادہ اصول: اگر ڈیٹا اسکرین گھمانے پر دکھایا جانا چاہیے — یہ حالت ہے (StateFlow)۔ اگر اسکرین گھمانے پر واقعہ دہرایا نہیں جانا چاہیے — یہ ایک بار استعمال ہونے والا واقعہ ہے (SharedFlow)۔ مثال کے طور پر، «خرابی کے پیغام کا ٹوسٹ» — SharedFlow: اسکرین گھمانے پر ٹوسٹ دوبارہ نہیں دکھایا جانا چاہیے۔ «مصنوعات کی فہرست» — StateFlow: اسکرین گھمانے پر فہرست اسکرین پر رہنی چاہیے۔
IT Sectr میں ہم SharedFlow استعمال کرتے ہیں: نیویگیشن کمانڈز (اسکرین پر جانا، ڈیپ لنک کھولنا)، UI واقعات (Snackbar, AlertDialog)، سسٹم نوٹیفیکیشن (پس منظر میں ڈیٹا اپ ڈیٹ، ادائیگی کا نتیجہ)، تجزیاتی واقعات (لاگنگ، ٹریکنگ)۔
MutableSharedFlow واقعات بھیجنے کے لیے emit() (suspend) اور tryEmit()(non-suspend) طریقوں کے ساتھ SharedFlow کا قابل تبدیل ورژن ہے۔ اگر بفر بھرا ہوا ہو اور onBufferOverflow = SUSPEND ہو تو emit() معطل ہو جاتا ہے۔ tryEmit() Boolean لوٹاتا ہے — واقعہ بفر میں کامیابی سے شامل ہوا یا نہیں۔
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
}
تعمیری پیرامیٹرز اہم ہیں: replay = 0 اس بات کی ضمانت دیتا ہے کہ واقعہ نئے سبسکرائبر کے لیے دہرایا نہیں جائے گا؛ extraBufferCapacity = 10 — UI کے سبسکرائب ہونے سے پہلے تیز واقعہ بھیجنے کی صورت میں بفر؛ DROP_OLDEST — بھرنے پر حکمت عملی: پرانے واقعات ضائع ہو جاتے ہیں، نئے محفوظ رہتے ہیں۔ Kotlin Coroutines Performance (JetBrains, 2024) کے مطابق، extraBufferCapacity = 64 کے ساتھ SharedFlow بغیر نقصان کے فی سیکنڈ 100,000 سے زیادہ واقعات پر کارروائی کرتا ہے۔
Event پیٹرن (یا UiEvent) — ViewModel سے View میں ایک بار استعمال ہونے والے واقعات کی منتقلی کے لیے Google کا تجویز کردہ طریقہ۔ حالت (StateFlow) کے برعکس، واقعہ کو صرف ایک بار پروسیس کیا جانا چاہیے، اور اسکرین گھمانے پر اسے دہرایا نہیں جانا چاہیے۔ replay = 0 کے ساتھ SharedFlow اس کام کے لیے مثالی ہے۔
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 ?: "آرڈر کی خرابی"))
}
}
}
}
sealed interface CheckoutEvent {
data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}
View میں (Activity/Fragment): واقعہ کی سبسکرپشن lifecycleScope میں repeatOnLifecycle(STATE.STARTED) کے ساتھ کی جانی چاہیے۔ STARTED میں ہر داخلے پر سبسکرپشن دوبارہ بنتی ہے، لیکن واقعہ دہرایا نہیں جاتا کیونکہ replay=0 والا SharedFlow اسے پہلے ہی جاری کر چکا ہے۔ یہ اس بات کی ضمانت دیتا ہے کہ آرڈر ٹریکنگ اسکرین پر نیویگیشن ہر اسکرین گھمانے پر نہیں بلکہ صرف ایک بار ہوگی۔
MutableSharedFlow تعمیر کنندہ تین پیرامیٹرز لیتا ہے جو بفر کے رویے کا تعین کرتے ہیں۔ غلط ترتیب واقعات کے نقصان یا emit() کی مسدودی کا سبب بن سکتی ہے۔
| پیرامیٹر | قسم | پہلے سے طے شدہ | وضاحت |
|---|---|---|---|
| replay | Int | 0 | نئے سبسکرائبر کو دوبارہ چلائے جانے والے تازہ ترین واقعات کی تعداد۔ 0 = دوبارہ نہ چلائیں، 1 = StateFlow کی طرح |
| extraBufferCapacity | Int | 0 | replay کے علاوہ اضافی بفر۔ واقعات سرکلر بفر میں محفوظ ہوتے ہیں۔ 64 — زیادہ تر منظرناموں کے لیے تجویز کردہ حد |
| onBufferOverflow | BufferOverflow | SUSPEND | بفر بھرنے پر حکمت عملی: SUSPEND, DROP_OLDEST, DROP_LATEST |
// مختلف منظرناموں کے لیے ترتیبات:
// 1. ایک بار استعمال ہونے والے UI واقعات (نیویگیشن، ٹوسٹ)
val uiEvents = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 5,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 2. حالت کی مطابقت پذیری کے لیے Replay بہاؤ (StateFlow کی طرح)
val stateLike = MutableSharedFlow<AppState>(
replay = 1,
extraBufferCapacity = 0
)
// 3. زیادہ فریکوئنسی والے واقعات کی ترسیل (تجزیہ، لاگز)
val analytics = MutableSharedFlow<AnalyticsEvent>(
replay = 0,
extraBufferCapacity = 100,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
اہم: extraBufferCapacity + replay = کل بفر سائز۔ اگر emit() سبسکرائبر کے واقعہ پر کارروائی کرنے سے زیادہ تیزی سے بلایا جائے تو بفر بھر جاتا ہے اور onBufferOverflow عمل میں آتا ہے۔ UI واقعات کے لیے DROP_OLDEST ایک محفوظ حکمت عملی ہے: پرانے واقعات (جو اب متعلقہ نہیں ہیں) نئے کے حق میں ضائع کر دیے جاتے ہیں۔ مالی لین دین کے لیے SUSPEND استعمال کریں — یہ بھیجنے والے کو مسدود کرنے کی قیمت پر کسی واقعہ کے ضائع نہ ہونے کی ضمانت دیتا ہے۔
نیویگیشن کمانڈز SharedFlow کے لیے کلاسک استعمال کا معاملہ ہیں۔ Fragment واقعات کو سبسکرائب کرکے نیویگیشن انجام دیتا ہے۔ اسکرین گھمانے پر کمانڈ دہرائی نہیں جاتی۔
// 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 میں:
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()
}
}
}
}
پیچیدہ منظرنامہ: UI کے لیے StateFlow کے ساتھ ملا کر پس منظر کے واقعات کی اطلاع کے لیے 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("نئی اطلاع: ${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("تمام اطلاعات پڑھی ہوئی نشان زد")
}
}
}
اس مثال میں: StateFlow اطلاعوں کی فہرست محفوظ کرتا ہے (حالت — اسکرین گھمانے پر محفوظ رہتی ہے)، SharedFlow ٹوسٹ پیغامات بھیجتا ہے (ایک بار استعمال ہونے والے واقعات — اسکرین گھمانے پر دہرائے نہیں جاتے)۔ دو Flow اقسام کا مجموعہ 2022 سے Google کا ViewModel کے لیے تجویز کردہ پیٹرن ہے۔
اکثر پوچھے گئے سوالات
ہاں، اگر بفر بھر جائے اور onBufferOverflow = DROP_OLDEST یا DROP_LATEST ہو۔ SharedFlow ہر واقعہ کی ترسیل کی ضمانت نہیں دیتا — یہ پیغام کی قطار (جیسے Channel) نہیں ہے۔ اگر تمام واقعات کی ضمانتی ترسیل درکار ہو تو نہ بھرنے والے بفر (UNLIMITED) والا Channel یا BroadcastChannel (متروک) استعمال کریں۔ UI واقعات کے لیے پرانے واقعات کا کھو جانا (مثال کے طور پر، پرانی نیویگیشن) متوقع رویہ ہے، خرابی نہیں۔
Channel ایک FIFO قطار ہے جہاں ہر واقعہ صرف ایک سبسکرائبر کو پہنچایا جاتا ہے (نقطہ سے نقطہ)۔ SharedFlow ایک نشریات ہے: ہر واقعہ تمام فعال سبسکرائبرز کو پہنچایا جاتا ہے۔ SharedFlow BroadcastChannel (جو متروک ہے) کے قریب ہے اور «ایک سے کئی» منظرناموں کے لیے موزوں ہے۔ Channel «ایک سے ایک» کے لیے ہے (تھریڈ پولز، pipeline)۔ JetBrains کی سفارش کے مطابق، SharedFlow تمام نئے پروجیکٹس کے لیے BroadcastChannel کا متبادل ہے۔
SharedFlow پہلے سے تھریڈ سیف ہے — emit() اور collect() درست طریقے سے ہم آہنگ ہیں۔ متعدد تھریڈز بغیر لاک کے emit() کال کر سکتے ہیں اور تمام فعال سبسکرائبرز واقعات کو صحیح ترتیب میں وصول کریں گے۔ tryEmit() غیر مسدود ہے — اگر بفر بھرا ہوا ہو تو false لوٹاتا ہے۔ زیادہ بوجھ والے نظاموں کے لیے DROP_OLDEST کے ساتھ tryEmit() استعمال کریں — یہ تھریڈز کی مسدودی کو روکتا ہے۔
replay=1 کے بغیر SharedFlow آخری قدر محفوظ نہیں کرتا — اسکرین گھمانے پر نیا سبسکرائبر موجودہ حالت حاصل نہیں کرے گا، UI خالی رہے گا۔ replay=1 کے ساتھ SharedFlow StateFlow کی طرح برتاؤ کرتا ہے لیکن equals() موازنہ کی اصلاح کھو دیتا ہے، جس کی وجہ سے ایک ہی قدر کے دوبارہ بھیجنے پر اضافی اطلاعیں آتی ہیں۔ StateFlow حالت کے لیے صحیح انتخاب ہے؛ SharedFlow واقعات کے لیے ہے۔
SharedFlow کو جانچنے کے لیے Turbine استعمال کریں — Flow کی جانچ کے لیے Kotlin لائبریری۔ Turbine آپ کو ہر ایک اخراج کو علیحدہ علیحدہ ٹائم آؤٹ اور تکمیل کی جانچ کے ساتھ تصدیق کرنے کی اجازت دیتا ہے۔ مثال: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }۔ نیز متوقع واقعات کی تعداد بتا کر runTest میں .toList() استعمال کیا جا سکتا ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں