StateFlow و SharedFlow رابطهای برنامهنویسی کاربردی (API) برای Flow هستند که به Flowها این امکان را میدهند که بهروزرسانیهای وضعیت را به طور بهینه منتشر کنند و مقادیر را به چندین مصرفکننده ارسال کنند.
StateFlow
StateFlow یک جریان قابل مشاهده با قابلیت نگهداری وضعیت است که بهروزرسانیهای وضعیت فعلی و جدید را به جمعکنندههای خود منتشر میکند. مقدار وضعیت فعلی را میتوان از طریق ویژگی value آن نیز خواند. برای بهروزرسانی وضعیت و ارسال آن به جریان، یک مقدار جدید به ویژگی value کلاس MutableStateFlow اختصاص دهید.
در اندروید، StateFlow برای کلاسهایی که نیاز به حفظ یک وضعیت قابل مشاهده و قابل تغییر دارند، بسیار مناسب است.
با پیروی از مثالهای جریانهای کاتلین ، یک 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 است، تولیدکننده (producer) نام دارد و تمام کلاسهایی که از StateFlow جمعآوری میکنند، مصرفکننده (consumers) هستند. برخلاف یک جریان سرد (cold flow) که با استفاده از سازنده flow builder) ساخته شده است، یک StateFlow داغ (hot) است: جمعآوری از جریان، هیچ کد تولیدکنندهای را فعال نمیکند. یک StateFlow همیشه فعال و در حافظه است و فقط زمانی واجد شرایط جمعآوری زباله (garbage collection) میشود که هیچ ارجاع دیگری از ریشه جمعآوری زباله به آن وجود نداشته باشد.
وقتی یک مصرفکننده جدید شروع به جمعآوری از جریان میکند، آخرین وضعیت در جریان و هر وضعیت بعدی را دریافت میکند. میتوانید این رفتار را در کلاسهای قابل مشاهده دیگر مانند 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 ایجاد شده در جریانهای کاتلین به عنوان مثال، به جای اینکه هر جمعکننده یک جریان جدید ایجاد کند، میتوانید دادههای بازیابی شده از Firestore را با استفاده از shareIn بین جمعکنندهها به اشتراک بگذارید. برای این کار باید موارد زیر را ارسال کنید:
- یک
CoroutineScopeکه برای اشتراکگذاری جریان استفاده میشود. این scope باید بیشتر از هر consumer عمر کند تا جریان مشترک تا زمانی که لازم است، فعال بماند. - تعداد آیتمهایی که باید برای هر جمعآوریکننده جدید دوباره پخش شوند.
- سیاست رفتار آغازین.
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 برای شروع اشتراکگذاری پس از ظاهر شدن اولین مشترک و فعال نگه داشتن جریان برای همیشه.
جریان اشتراکی
تابع shareIn یک SharedFlow برمیگرداند، یک جریان داغ که مقادیر را به تمام مصرفکنندگانی که از آن جمعآوری میکنند، منتشر میکند. SharedFlow یک تعمیم بسیار قابل تنظیم از StateFlow است.
شما میتوانید بدون استفاده از shareIn یک SharedFlow ایجاد کنید. به عنوان مثال، میتوانید از SharedFlow برای ارسال tickها به بقیه برنامه استفاده کنید تا همه محتوا به صورت دورهای و همزمان بهروزرسانی شوند. جدا از دریافت آخرین اخبار، ممکن است بخواهید بخش اطلاعات کاربر را با مجموعه موضوعات مورد علاقهاش بهروزرسانی کنید. در قطعه کد زیر، یک 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 است.
منابع جریان اضافی
- جریانهای کاتلین در اندروید
- تست جریانهای کاتلین روی اندروید
- نکاتی که باید در مورد عملگرهای shareIn و stateIn در Flow بدانید
- مهاجرت از LiveData به Kotlin Flow
- منابع اضافی برای کوروتینها و جریان کاتلین