StateFlow وSharedFlow

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

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 View 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 : AppCompatActivity() {
    private val latestNewsViewModel = // 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<Event<String>> = _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 إذا كنت لا تريد إعادة تشغيل أحدث المعلومات التي تم إرسالها إلى التدفق.

موارد التدفق الإضافية