StateFlow وSharedFlow

‫StateFlow وSharedFlow هما واجهتا برمجة تطبيقات Flow اللتان تتيحان لعمليات Flow إرسال تحديثات الحالة وإرسال القيم إلى عدة مستهلكين على النحو الأمثل.

StateFlow

StateFlow هي عبارة عن تدفق قابل للمراقبة ومخزّن للحالة، ويعرض تعديلات الحالة الحالية والجديدة للمجمّعات. يمكن أيضًا قراءة قيمة الحالة الحالية من خلال السمة value. لتعديل الحالة وإرسالها إلى التدفق، عليك تعيين قيمة جديدة للسمة value في الفئة MutableStateFlow.

في Android، StateFlow هي خيار مناسب جدًا للفئات التي تحتاج إلى الاحتفاظ بحالة قابلة للتغيير ويمكن رصدها.

باتّباع الأمثلة الواردة في تدفّقات Kotlin، يمكن عرض StateFlow من LatestNewsViewModel ليتمكّن 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() إلى إلغاء تسجيل المستهلك تلقائيًا عندما تنتقل طريقة العرض إلى الحالة STOPPED، بينما لا يؤدي جمع البيانات من StateFlow أو أي مسار آخر إلى إيقاف عملية الجمع تلقائيًا. لتحقيق السلوك نفسه، عليك جمع التدفق من حظر Lifecycle.repeatOnLifecycle.

تحويل المسارات الباردة إلى مسارات رائجة باستخدام shareIn

StateFlow هو تدفق نشط، أي أنّه يظل في الذاكرة طالما يتم جمع التدفق أو طالما توجد أي مراجع أخرى إليه من جذر جمع البيانات المُهمَلة. يمكنك تحويل التدفقات الباردة إلى تدفقات ساخنة باستخدام عامل التشغيل shareIn.

باستخدام callbackFlow الذي تم إنشاؤه في تدفقات Kotlin كمثال، بدلاً من أن ينشئ كل جامع بيانات تدفقًا جديدًا، يمكنك مشاركة البيانات التي تم استردادها من Firestore بين جامعي البيانات باستخدام shareIn. يجب إدخال ما يلي:

  • 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.

يمكنك إنشاء SharedFlow بدون استخدام shareIn. على سبيل المثال، يمكنك استخدام 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 وظيفة إذا كنت لا تريد إعادة تشغيل آخر المعلومات التي تم إرسالها إلى التدفق.

مراجع إضافية حول مسار المستخدم