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 सब्सक्राइबर सिंक्रनाइज़ेशन के लिए फ़ाइन-ग्रेन लॉकिंग का उपयोग करता है और बिना प्रदर्शन गिरावट के 1000+ समवर्ती सब्सक्राइबर तक रैखिक स्केलेबिलिटी प्रदान करता है, जिसकी पुष्टि JetBrains परीक्षणों से हुई है।
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 SharedFlow का परिवर्तनशील संस्करण है जिसमें घटनाओं को भेजने के लिए emit() (suspend) और tryEmit() (गैर-suspend) विधियाँ हैं। emit() निलंबित हो जाता है यदि बफ़र भरा है और onBufferOverflow = SUSPEND है। 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. स्थिति सिंक्रनाइज़ेशन के लिए रीप्ले फ़्लो (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 से ViewModel के लिए Google का अनुशंसित पैटर्न है।
अक्सर पूछे जाने वाले प्रश्न
हाँ, यदि बफ़र भरा हुआ है और onBufferOverflow = DROP_OLDEST या DROP_LATEST है। SharedFlow प्रत्येक घटना की डिलीवरी की गारंटी नहीं देता — यह संदेश कतार (जैसे Channel) नहीं है। यदि आपको सभी घटनाओं की गारंटीकृत डिलीवरी चाहिए, तो असीमित बफ़र (UNLIMITED) या BroadcastChannel (बहिष्कृत) वाले Channel का उपयोग करें। UI घटनाओं के लिए, पुरानी घटनाओं का खो जाना (जैसे, पुराना नेविगेशन) अपेक्षित व्यवहार है, बग नहीं।
Channel एक FIFO कतार है जहाँ प्रत्येक घटना ठीक एक सब्सक्राइबर को दी जाती है (बिंदु-से-बिंदु)। SharedFlow एक प्रसारण है: प्रत्येक घटना सभी सक्रिय सब्सक्राइबर को दी जाती है। SharedFlow BroadcastChannel (जो बहिष्कृत है) के करीब है और एक-से-अनेक परिदृश्यों के लिए उपयुक्त है। Channel एक-से-एक (थ्रेड पूल, पाइपलाइन) के लिए है। 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 एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें