StateFlow و SharedFlow

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

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