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
- عمليات