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

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