SharedFlow: এটি কী, Android-এ SharedFlow vs StateFlow

লেখক: IT Sectr প্রকাশিত: 2026-02-20 পড়ার সময়: 9 মিনিট

SharedFlow হল Kotlin Coroutines লাইব্রেরির একটি হট রিঅ্যাকটিভ ফ্লো, যা একবার ঘটে এমন ঘটনার (one-shot events) জন্য অপ্টিমাইজ করা হয়েছে যা স্ক্রিন রোটেশন বা সাবস্ক্রাইবার পুনঃনির্মাণে পুনরাবৃত্তি হওয়া উচিত নয়। আমরা দেখাই কিভাবে SharedFlow StateFlow থেকে আলাদা: StateFlow-এর বিপরীতে, SharedFlow নতুন সাবস্ক্রাইবারদের জন্য শেষ মান সংরক্ষণ করে না এবং replay, extraBufferCapacity এবং onBufferOverflow কনফিগারেশন সমর্থন করে। Google (Android Developers, 2025) অনুসারে, SharedFlow হল নেভিগেশন কমান্ড, Snackbar বার্তা এবং অন্যান্য ইভেন্টের জন্য প্রস্তাবিত সমাধান যা ঠিক একবার প্রক্রিয়াজাত করা উচিত।

মূল পয়েন্ট

  • SharedFlow — একবার ঘটে এমন ঘটনার জন্য হট ফ্লো: নতুন সাবস্ক্রাইবাররা replay কনফিগারেশন ছাড়া পূর্ববর্তী মান পায় না।
  • MutableSharedFlow — ইভেন্ট পাঠানোর জন্য emit() এবং tryEmit() পদ্ধতি সহ পরিবর্তনযোগ্য সংস্করণ।
  • SharedFlow vs StateFlow: SharedFlow মান একত্রিত করে না (একাধিক মান বাফার করতে পারে), প্রাথমিক মান প্রয়োজন হয় না, একবার ঘটে এমন ঘটনার জন্য উপযুক্ত।
  • replay — নতুন সাবস্ক্রাইবারদের জন্য পুনরায় চালানো সাম্প্রতিক ইভেন্টের সংখ্যা (ডিফল্ট 0)।
  • extraBufferCapacity — replay-এর বাইরে ইভেন্টের জন্য অতিরিক্ত বাফার, emit() ব্লক করা প্রতিরোধ করে।

Kotlin-এ SharedFlow কী?

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 vs StateFlow: কখন কী ব্যবহার করবেন

SharedFlow এবং StateFlow-এর মধ্যে পছন্দ স্থানান্তরিত ডেটার শব্দার্থের উপর নির্ভর করে: অবস্থা (StateFlow) বা ইভেন্ট (SharedFlow)। নীচে উদাহরণ সহ স্পষ্ট মানদণ্ড দেওয়া হল।

মানদণ্ডSharedFlowStateFlow
শব্দার্থএকবার ঘটে এমন ঘটনা (নেভিগেশন, টোস্ট, সতর্কতা)UI অবস্থা (তালিকা, লোডিং, ত্রুটি)
প্রাথমিক মানপ্রয়োজন নেইপ্রয়োজন
সাবস্ক্রিপশনে পুনরায় চালানোশুধুমাত্র যদি replay > 0সর্বদা শেষ মান
একত্রীকরণনা — ইভেন্ট হারায় না (যদি বাফার পূর্ণ না হয়)হ্যাঁ — শুধুমাত্র সর্বশেষ সংরক্ষণ করে
বাফারিংreplay + extraBufferCapacity এর মাধ্যমে কনফিগারযোগ্যশুধুমাত্র 1 (replay=1 নির্দিষ্ট)
ব্যবহারnavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

সবচেয়ে সহজ নিয়ম: যদি ডেটা স্ক্রিন রোটেশনে দেখানো উচিত — এটি অবস্থা (StateFlow)। যদি স্ক্রিন রোটেশনে ইভেন্টটি পুনরাবৃত্তি না হওয়া উচিত — এটি একবার ঘটে এমন ইভেন্ট (SharedFlow)। উদাহরণস্বরূপ, ত্রুটি বার্তা সহ একটি টোস্ট — SharedFlow: রোটেশনে টোস্ট আবার দেখা উচিত নয়। একটি পণ্য তালিকা — StateFlow: রোটেশনে তালিকাটি স্ক্রিনে থাকা উচিত।

IT Sectr-এ আমরা SharedFlow ব্যবহার করি: নেভিগেশন কমান্ড (স্ক্রিন ট্রানজিশন, ডিপ লিংক খোলা), UI ইভেন্ট (Snackbar, AlertDialog), সিস্টেম নোটিফিকেশন (ব্যাকগ্রাউন্ড ডেটা আপডেট, পেমেন্ট ফলাফল), বিশ্লেষণ ইভেন্ট (লগিং, ট্র্যাকিং)।

MutableSharedFlow: emit, tryEmit এবং বাফারিং

MutableSharedFlow হল SharedFlow-এর পরিবর্তনযোগ্য সংস্করণ যাতে ইভেন্ট পাঠানোর জন্য emit() (suspend) এবং tryEmit() (নন-suspend) পদ্ধতি রয়েছে। emit() স্থগিত হয় যদি বাফার পূর্ণ হয় এবং onBufferOverflow = SUSPEND হয়। tryEmit() একটি Boolean ফেরত দেয় যা নির্দেশ করে যে ইভেন্টটি সফলভাবে বাফারে যুক্ত হয়েছে কিনা।

kotlin
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-এর বেশি ইভেন্ট প্রক্রিয়া করে।

একবার ঘটে এমন ঘটনার জন্য SharedFlow: ইভেন্ট প্যাটার্ন

ইভেন্ট প্যাটার্ন (বা UiEvent) হল ViewModel থেকে View-এ একবার ঘটে এমন ইভেন্ট পাঠানোর Google-এর প্রস্তাবিত উপায়। অবস্থার (StateFlow) বিপরীতে, একটি ইভেন্ট ঠিক একবার প্রক্রিয়াজাত করা উচিত এবং স্ক্রিন রোটেশনে এটি পুনরাবৃত্তি হওয়া উচিত নয়। replay = 0 সহ SharedFlow এই কাজের জন্য আদর্শ।

kotlin
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 এটি ইতিমধ্যেই মুক্তি দিয়েছে। এটি নিশ্চিত করে যে অর্ডার ট্র্যাকিং স্ক্রিনে নেভিগেশন শুধুমাত্র একবার ঘটে, প্রতিটি রোটেশনে নয়।

SharedFlow প্যারামিটার: replay, extraBufferCapacity, onBufferOverflow

MutableSharedFlow কনস্ট্রাক্টর তিনটি প্যারামিটার গ্রহণ করে যা বাফার আচরণ নির্ধারণ করে। ভুল কনফিগারেশন ইভেন্ট হারানো বা emit() ব্লকিং হতে পারে।

প্যারামিটারপ্রকারডিফল্টবিবরণ
replayInt0নতুন সাবস্ক্রাইবারের জন্য পুনরায় চালানো সাম্প্রতিক ইভেন্টের সংখ্যা। 0 = পুনরায় চালাবে না, 1 = StateFlow-এর মতো
extraBufferCapacityInt0replay-এর বাইরে অতিরিক্ত বাফার। ইভেন্টগুলি একটি বৃত্তাকার বাফারে সংরক্ষণ করা হয়। 64 অধিকাংশ পরিস্থিতির জন্য প্রস্তাবিত সীমা
onBufferOverflowBufferOverflowSUSPENDবাফার পূর্ণ হলে কৌশল: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// বিভিন্ন পরিস্থিতির জন্য কনফিগারেশন:

// 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 ব্যবহার করুন — এটি নিশ্চিত করে যে প্রেরককে ব্লক করার মূল্যে কোনো ইভেন্ট হারানো না যায়।

কোড উদাহরণ: Kotlin-এ SharedFlow

উদাহরণ 1: Jetpack Navigation-এর সাথে নেভিগেশনের জন্য SharedFlow

নেভিগেশন কমান্ডগুলি SharedFlow-এর জন্য একটি ক্লাসিক ব্যবহারের ক্ষেত্রে। Fragment ইভেন্টগুলিতে সাবস্ক্রাইব করে এবং নেভিগেশন সম্পাদন করে। স্ক্রিন রোটেশনে, কমান্ডটি পুনরাবৃত্তি হয় না।

kotlin
// 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()
            }
        }
    }
}

উদাহরণ 2: Room এবং Flow অপারেটরগুলির সাথে SharedFlow

একটি জটিল পরিস্থিতি: UI-এর জন্য StateFlow-এর সাথে মিলিত ব্যাকগ্রাউন্ড ইভেন্ট বিজ্ঞপ্তিগুলির জন্য SharedFlow।

kotlin
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-এর প্রস্তাবিত প্যাটার্ন।

সচরাচর জিজ্ঞাসিত প্রশ্ন

SharedFlow কি একটি ইভেন্ট হারাতে পারে?

হ্যাঁ, যদি বাফার পূর্ণ থাকে এবং onBufferOverflow = DROP_OLDEST বা DROP_LATEST হয়। SharedFlow প্রতিটি ইভেন্টের ডেলিভারির গ্যারান্টি দেয় না — এটি একটি বার্তা সারি (Channel-এর মতো) নয়। যদি আপনার সকল ইভেন্টের গ্যারান্টিযুক্ত ডেলিভারির প্রয়োজন হয়, তাহলে সীমাহীন বাফার (UNLIMITED) বা BroadcastChannel (অপ্রচলিত) সহ Channel ব্যবহার করুন। UI ইভেন্টের জন্য, পুরানো ইভেন্ট হারানো (যেমন, পুরানো নেভিগেশন) প্রত্যাশিত আচরণ, বাগ নয়।

SharedFlow Channel থেকে কীভাবে আলাদা?

Channel হল একটি FIFO সারি যেখানে প্রতিটি ইভেন্ট ঠিক একজন সাবস্ক্রাইবারকে সরবরাহ করা হয় (পয়েন্ট-টু-পয়েন্ট)। SharedFlow হল একটি সম্প্রচার: প্রতিটি ইভেন্ট সমস্ত সক্রিয় সাবস্ক্রাইবারকে সরবরাহ করা হয়। SharedFlow BroadcastChannel-এর (যা অপ্রচলিত) কাছাকাছি এবং এক-থেকে-অনেক পরিস্থিতির জন্য উপযুক্ত। Channel এক-থেকে-এক (থ্রেড পুল, পাইপলাইন) এর জন্য। JetBrains-এর সুপারিশ অনুসারে, SharedFlow হল সমস্ত নতুন প্রকল্পে BroadcastChannel-এর প্রতিস্থাপন।

কিভাবে SharedFlow-কে থ্রেড-সেফ করবেন?

SharedFlow ইতিমধ্যেই থ্রেড-সেফ — emit() এবং collect() সঠিকভাবে সিঙ্ক্রোনাইজ করা আছে। একাধিক থ্রেড লক ছাড়াই emit() কল করতে পারে এবং সমস্ত সক্রিয় সাবস্ক্রাইবার সঠিক ক্রমে ইভেন্ট পায়। tryEmit() নন-ব্লকিং — বাফার পূর্ণ হলে false ফেরত দেয়। উচ্চ-লোড সিস্টেমের জন্য, DROP_OLDEST সহ tryEmit() ব্যবহার করুন — এটি থ্রেড ব্লকিং প্রতিরোধ করে।

কেন SharedFlow অবস্থার জন্য ব্যবহার করা হয় না?

replay=1 ছাড়া SharedFlow শেষ মান সংরক্ষণ করে না — স্ক্রিন রোটেশনে একটি নতুন সাবস্ক্রাইবার বর্তমান অবস্থা পাবে না এবং UI খালি থাকবে। replay=1 সহ, SharedFlow StateFlow-এর মতো আচরণ করে কিন্তু equals() এর মাধ্যমে তুলনা অপ্টিমাইজেশন হারায়, যার ফলে একই মান পুনরায় নির্গত হলে অপ্রয়োজনীয় বিজ্ঞপ্তি হয়। StateFlow অবস্থার জন্য সঠিক পছন্দ; SharedFlow ইভেন্টের জন্য।

কিভাবে SharedFlow পরীক্ষা করবেন?

SharedFlow পরীক্ষার জন্য, Turbine ব্যবহার করুন — Flow পরীক্ষার জন্য একটি Kotlin লাইব্রেরি। Turbine টাইমআউট এবং সমাপ্তি যাচাই সহ প্রতিটি নির্গমন পৃথকভাবে পরীক্ষা করার অনুমতি দেয়। উদাহরণ: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }। আপনি প্রত্যাশিত ইভেন্টের সংখ্যা উল্লেখ করে runTest-এ .toList() ব্যবহার করতে পারেন।

সারসংক্ষেপ

  • SharedFlow — শেষ অবস্থার সাথে আবদ্ধ নয় এমন একবার ঘটে এমন ইভেন্টের জন্য একটি হট রিঅ্যাকটিভ ফ্লো।
  • SharedFlow vs StateFlow: SharedFlow ইভেন্টের জন্য (নেভিগেশন, টোস্ট, সতর্কতা), StateFlow অবস্থার জন্য (তালিকা, লোডিং, ত্রুটি)।
  • MutableSharedFlow replay=0, extraBufferCapacity=5, DROP_OLDEST সহ UI ইভেন্টের জন্য স্ট্যান্ডার্ড কনফিগারেশন।
  • emit() — ব্লকিং নির্গমনের জন্য suspend ফাংশন; tryEmit() — Boolean ফলাফল সহ নন-suspend।
  • sealed class সহ UiEvent প্যাটার্ন হল ViewModel থেকে View-এ একবার ঘটে এমন ইভেন্ট পাঠানোর Google-এর প্রস্তাবিত উপায়।
  • SharedFlow প্রত্যেক সাবস্ক্রাইবারকে ডেলিভারির গ্যারান্টি দেয়, কিন্তু বাফার ওভারফ্লোতে প্রত্যেক ইভেন্টের ডেলিভারির গ্যারান্টি দেয় না
  • একক ViewModel-এ SharedFlow + StateFlow-এর সংমিশ্রণ হল অবস্থা এবং ইভেন্ট পৃথককারী আর্কিটেকচারের জন্য সর্বোত্তম প্যাটার্ন।

আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব

IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।

প্রকল্প নিয়ে আলোচনা করুন

আরও পড়ুন