StateFlow وSharedFlow

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

StateFlow

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

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

باتّباع الأمثلة الواردة في عمليات Kotlin، يمكن عرض StateFlow من LatestNewsViewModel حتى تتمكّن View من الاستماع إلى تعديلات حالة واجهة المستخدم وجعل حالة الشاشة تبقى intact عند تغيير الإعدادات.

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 كأحد مثالي، بدلاً من أن ينشئ كلّ مجمّع عملية جديدة، يمكنك مشاركة data التي تم استرجاعها من 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 إذا كنت لا تريد إعادة تشغيل آخر المعلومات المُرسَلة إلى العملية.

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