StateFlow و SharedFlow

‫StateFlow و SharedFlow میاناهای برنامه‌سازی کاربردی 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 به آن نیاز ندارد.
  • وقتی نما به حالت STOPPED می‌رود، LiveData.observe() به‌طور خودکار ثبت مصرف‌کننده را لغو می‌کند، درحالی‌که جمع‌آوری از StateFlow یا هر جریان دیگری به‌طور خودکار متوقف نمی‌شود. برای دستیابی به همان عملکرد، باید جریان را از Lifecycle.repeatOnLifecycle بلوک جمع‌آوری کنید.

درحال داغ کردن جریان‌های سرد بااستفاده از shareIn

جریان StateFlow یک جریان داغ است—تا زمانی که جریان جمع‌آوری شود یا تا زمانی که هرگونه مرجع دیگری به آن از ریشه جمع‌آوری زباله وجود داشته باشد، در حافظه باقی می‌ماند. بااستفاده از shareIn اپراتور می‌توانید جریان‌های سرد را داغ کنید.

بااستفاده از callbackFlow ایجادشده در جریان‌های Kotlin به‌عنوان مثال، به‌جای اینکه هر جمع‌آوری‌کننده جریان جدیدی ایجاد کند، می‌توانید بااستفاده از shareIn داده‌های بازیابی‌شده از Firestore را بین جمع‌آوری‌کننده‌ها هم‌رسانی کنید. باید موارد زیر را ارسال کنید:

  • 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 است.

منابع جریان اضافی