StateFlow और SharedFlow

StateFlow और SharedFlow, फ़्लो एपीआई हैं. इनकी मदद से, फ़्लो स्टेट अपडेट को ऑप्टिमाइज़ तरीके से और वैल्यू को कई उपभोक्ताओं तक पहुंचा सकते हैं.

StateFlow

StateFlow एक स्टेट-होल्डर ऑब्ज़र्वेबल फ़्लो है. यह अपने कलेक्टर को मौजूदा और नई स्थिति के अपडेट भेजता है. value प्रॉपर्टी के ज़रिए भी, मौजूदा स्थिति की वैल्यू पढ़ी जा सकती है. स्टेट को अपडेट करने और उसे फ़्लो में भेजने के लिए, MutableStateFlow क्लास की value प्रॉपर्टी को नई वैल्यू असाइन करें.

Android में, StateFlow उन क्लास के लिए सबसे सही है जिन्हें बदलाव किया जा सकने वाली ऐसी स्थिति बनाए रखनी होती है जिसे देखा जा सकता है.

Kotlin फ़्लो के उदाहरणों के मुताबिक, LatestNewsViewModel से StateFlow को ऐक्सेस किया जा सकता है, ताकि View, यूज़र इंटरफ़ेस (यूआई) की स्थिति के अपडेट सुन सके. साथ ही, कॉन्फ़िगरेशन में बदलाव होने पर भी स्क्रीन की स्थिति को बनाए रख सके.

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update UI with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>) : LatestNewsUiState()
    data class Error(val exception: Throwable) : LatestNewsUiState()
}

MutableStateFlow को अपडेट करने के लिए ज़िम्मेदार क्लास, प्रोड्यूसर होती है. वहीं, StateFlow से डेटा इकट्ठा करने वाली सभी क्लास, कंज्यूमर होती हैं. flow बिल्डर का इस्तेमाल करके बनाए गए कोल्ड फ़्लो के उलट, StateFlow हॉट होता है: फ़्लो से डेटा इकट्ठा करने पर, कोई भी प्रोड्यूसर कोड ट्रिगर नहीं होता. StateFlow हमेशा चालू रहता है और मेमोरी में मौजूद रहता है. यह सिर्फ़ तब कचरा इकट्ठा करने के लिए उपलब्ध होता है, जब कचरा इकट्ठा करने वाले रूट से इसके कोई अन्य रेफ़रंस न हों.

जब कोई नया उपभोक्ता, फ़्लो से डेटा इकट्ठा करना शुरू करता है, तो उसे स्ट्रीम में मौजूद आखिरी स्थिति और उसके बाद की सभी स्थितियां मिलती हैं. इस तरह के व्यवहार को LiveData जैसी अन्य क्लास में भी देखा जा सकता है.

View, किसी अन्य फ़्लो की तरह ही StateFlow को सुनता है:

class LatestNewsActivity : ComponentActivity() {
    private val latestNewsViewModel: LatestNewsViewModel = TODO() // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

किसी भी फ़्लो को StateFlow में बदलने के लिए, stateIn इंटरमीडिएट ऑपरेटर का इस्तेमाल करें.

StateFlow, Flow, और LiveData

StateFlow और LiveData में समानताएं हैं. ये दोनों, डेटा होल्डर क्लास हैं. साथ ही, ऐप्लिकेशन आर्किटेक्चर में इस्तेमाल किए जाने पर, दोनों एक जैसे पैटर्न को फ़ॉलो करती हैं.

हालांकि, ध्यान दें कि StateFlow और LiveData अलग-अलग तरीके से काम करते हैं:

  • StateFlow के कंस्ट्रक्टर में शुरुआती स्थिति को पास करना ज़रूरी है, जबकि LiveData के कंस्ट्रक्टर में ऐसा करना ज़रूरी नहीं है.
  • LiveData.observe() की स्थिति में जाने पर, उपभोक्ता अपने-आप LiveData.observe() से अनरजिस्टर हो जाता है. वहीं, StateFlow या किसी अन्य फ़्लो से डेटा इकट्ठा करने पर, डेटा अपने-आप इकट्ठा होना बंद नहीं होता.STOPPED इसी तरह का व्यवहार पाने के लिए, आपको Lifecycle.repeatOnLifecycle ब्लॉक से फ़्लो इकट्ठा करना होगा.

shareIn का इस्तेमाल करके, ठंडे फ़्लो को गर्म फ़्लो में बदलना

StateFlow एक हॉट फ़्लो है. यह तब तक मेमोरी में रहता है, जब तक फ़्लो इकट्ठा किया जाता है या जब तक कचरा इकट्ठा करने वाले रूट से इसके कोई अन्य रेफ़रंस मौजूद होते हैं. shareIn ऑपरेटर का इस्तेमाल करके, कोल्ड फ़्लो को हॉट फ़्लो में बदला जा सकता है.

Kotlin फ़्लो में बनाए गए callbackFlow का इस्तेमाल उदाहरण के तौर पर किया जा सकता है. हर कलेक्टर को नया फ़्लो बनाने के बजाय, shareIn का इस्तेमाल करके, Firestore से वापस पाए गए डेटा को कलेक्टर के बीच शेयर किया जा सकता है. आपको यह जानकारी देनी होगी:

  • एक CoroutineScope जिसका इस्तेमाल फ़्लो शेयर करने के लिए किया जाता है. यह स्कोप, किसी भी उपभोक्ता से ज़्यादा समय तक चालू रहना चाहिए, ताकि शेयर किए गए फ़्लो को ज़रूरत के मुताबिक चालू रखा जा सके.
  • हर नए कलेक्टर को फिर से चलाने के लिए आइटम की संख्या.
  • स्टार्ट बिहेवियर से जुड़ी नीति.

class NewsRemoteDataSource(
    private val externalScope: CoroutineScope
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

इस उदाहरण में, latestNews फ़्लो, नए कलेक्टर को आखिरी बार एमिट किया गया आइटम फिर से दिखाता है. साथ ही, यह तब तक चालू रहता है, जब तक externalScope चालू है और ऐक्टिव कलेक्टर मौजूद हैं. SharingStarted.WhileSubscribed() स्टार्ट नीति, अपस्ट्रीम प्रोड्यूसर को तब तक चालू रखती है, जब तक चालू सदस्य मौजूद हैं. शुरू करने की अन्य नीतियां भी उपलब्ध हैं. जैसे, SharingStarted.Eagerly प्रोड्यूसर को तुरंत शुरू करना या SharingStarted.Lazily पहला सदस्य दिखने के बाद शेयर करना शुरू करना और फ़्लो को हमेशा चालू रखना.

SharedFlow

shareIn फ़ंक्शन, SharedFlow दिखाता है. यह एक हॉट फ़्लो है, जो इससे डेटा इकट्ठा करने वाले सभी उपभोक्ताओं को वैल्यू भेजता है. SharedFlow, StateFlow का एक सामान्यीकरण है, जिसे आसानी से कॉन्फ़िगर किया जा सकता है.

shareIn का इस्तेमाल किए बिना भी SharedFlow बनाया जा सकता है. उदाहरण के लिए, SharedFlow का इस्तेमाल करके, ऐप्लिकेशन के बाकी हिस्सों को टिक भेजे जा सकते हैं. इससे, सभी कॉन्टेंट एक ही समय पर समय-समय पर रीफ़्रेश होते रहेंगे. ताज़ा खबरें फ़ेच करने के अलावा, आपको उपयोगकर्ता की जानकारी वाले सेक्शन को भी रीफ़्रेश करना पड़ सकता है. इसमें उसके पसंदीदा विषयों का कलेक्शन होता है. नीचे दिए गए कोड स्निपेट में, TickHandler SharedFlow को दिखाता है, ताकि अन्य क्लास को यह पता चल सके कि कॉन्टेंट को कब रीफ़्रेश करना है. StateFlow की तरह, क्लास में MutableSharedFlow टाइप की बैकिंग प्रॉपर्टी का इस्तेमाल करके, फ़्लो में आइटम भेजें:

// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Unit> = _tickFlow

    init {
        externalScope.launch {
            while (true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    // ...
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { /* ... */ }
    // ...
}

SharedFlow के व्यवहार को इन तरीकों से अपनी पसंद के मुताबिक बनाया जा सकता है:

  • replay की मदद से, नए सदस्यों के लिए पहले भेजी गई कई वैल्यू को फिर से भेजा जा सकता है.
  • onBufferOverflow की मदद से, यह तय किया जा सकता है कि जब बफ़र में भेजने के लिए आइटम भर जाएं, तो क्या करना है. डिफ़ॉल्ट वैल्यू BufferOverflow.SUSPEND है. इससे कॉल करने वाले व्यक्ति का कॉल निलंबित हो जाता है. अन्य विकल्प DROP_LATEST या DROP_OLDEST हैं.

MutableSharedFlow में एक subscriptionCount प्रॉपर्टी भी होती है. इसमें चालू कलेक्टर की संख्या होती है, ताकि आप अपने कारोबार के लॉजिक को उसके हिसाब से ऑप्टिमाइज़ कर सकें. MutableSharedFlow में एक resetReplayCache फ़ंक्शन भी होता है. इसका इस्तेमाल तब किया जाता है, जब आपको फ़्लो में भेजी गई नई जानकारी को फिर से नहीं चलाना होता.

फ़्लो के अन्य संसाधन