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
إذا كنت لا تريد إعادة تشغيل آخر المعلومات التي تم إرسالها إلى التدفق.
مراجع إضافية حول مسارات المستخدمين
- تدفّقات Kotlin على Android
- اختبار مسارات Kotlin على Android
- معلومات عن عاملي التشغيل shareIn وstateIn في Flow
- نقل البيانات من LiveData إلى Kotlin Flow
- مراجع إضافية حول الكوروتينات والتدفق في Kotlin