StateFlow و SharedFlow

StateFlow و SharedFlow API های Flow هستند که جریان ها را قادر می سازند تا به روز رسانی های حالت را به طور بهینه منتشر کنند و مقادیر را برای چندین مصرف کننده منتشر کنند.

StateFlow

StateFlow یک جریان قابل مشاهده توسط دارنده حالت است که به روز رسانی وضعیت فعلی و جدید را به جمع کننده های خود منتشر می کند. مقدار وضعیت فعلی را می توان از طریق ویژگی value آن نیز خواند. برای به‌روزرسانی وضعیت و ارسال آن به جریان، یک مقدار جدید به ویژگی value کلاس MutableStateFlow اختصاص دهید.

در اندروید، 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() به طور خودکار مصرف کننده را لغو ثبت می کند زمانی که view به حالت 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 است.

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