StateFlow dan SharedFlow

StateFlow dan SharedFlow adalah Flow API yang memungkinkan alur memunculkan pembaruan status dan nilai secara optimal ke beberapa konsumen.

StateFlow

StateFlow adalah alur yang dapat diamati pemegang status yang akan memunculkan pembaruan status saat ini dan yang baru kepada kolektornya. Nilai status saat ini juga dapat dibaca melalui properti value. Untuk memperbarui status dan mengirimkannya ke alur, tetapkan nilai baru ke properti value dari class MutableStateFlow.

Di Android, StateFlow sangat cocok untuk class yang perlu mempertahankan status yang dapat diubah dan diamati.

Dengan mengikuti contoh dari alur Kotlin, StateFlow dapat ditampilkan dari LatestNewsViewModel sehingga View dapat mendeteksi pembaruan status UI dan secara inheren membuat status layar tidak terpengaruh perubahan konfigurasi.

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()
}

Class yang bertanggung jawab untuk memperbarui MutableStateFlow adalah produser, dan semua class yang mengumpulkan dari StateFlow adalah konsumen. Tidak seperti alur dingin yang dibuat menggunakan builder flow, StateFlow bersifat panas: pengumpulan dari alur tidak memicu kode produser apa pun. StateFlow selalu aktif, berada dalam memori, dan valid untuk pembersihan sampah memori hanya jika tidak ada referensi lain terhadapnya dari root pembersihan sampah memori.

Saat konsumen baru mulai mengumpulkan dari flow, status terakhir dalam aliran data dan status berikutnya akan diterima. Anda dapat menemukan perilaku ini dalam class lain yang dapat diamati seperti LiveData.

View akan memproses StateFlow seperti pada alur lainnya:

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)
                    }
                }
            }
        }
    }
}

Untuk mengonversi flow apa pun menjadi StateFlow, gunakan operator perantara stateIn.

StateFlow, Alur, dan LiveData

StateFlow dan LiveData memiliki kemiripan. Keduanya merupakan class pemegang data yang dapat diamati, dan mengikuti pola serupa saat digunakan dalam arsitektur aplikasi.

Namun, perlu diperhatikan bahwa StateFlow dan LiveData berperilaku berbeda:

  • StateFlow memerlukan status awal untuk diteruskan ke konstruktor, sedangkan LiveData tidak.
  • LiveData.observe() akan otomatis membatalkan pendaftaran konsumen saat tampilan beralih ke status STOPPED, sedangkan pengumpulan dari StateFlow atau alur lainnya tidak menghentikan pengumpulan secara otomatis. Untuk mencapai perilaku yang sama, Anda harus mengumpulkan alur dari blok Lifecycle.repeatOnLifecycle.

Membuat flow cold menjadi hot menggunakan shareIn

StateFlow adalah flow hot, yang akan tetap berada di memori selama flow tersebut dikumpulkan atau saat referensi lain terhadapnya tersedia dari root pembersihan sampah memori. Anda dapat mengubah flow cold menjadi hot menggunakan operator shareIn.

Dengan menggunakan callbackFlow yang dibuat dalam flow Kotlin sebagai contoh, Anda dapat membagikan data yang diambil dari Firestore di antara kolektor menggunakan shareIn, bukan meminta setiap kolektor membuat flow baru. Anda harus meneruskan hal berikut:

  • CoroutineScope yang digunakan untuk membagikan alur. Cakupan ini harus lebih tahan lama dari konsumen mana pun agar alur bersama tetap aktif selama diperlukan.
  • Jumlah item yang akan diputar ulang ke setiap kolektor baru.
  • Kebijakan perilaku mulai.
class NewsRemoteDataSource(...,
    private val externalScope: CoroutineScope,
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

Dalam contoh ini, alur latestNews akan memutar ulang item yang terakhir dimunculkan ke kolektor baru dan akan tetap aktif selama externalScope masih aktif dan terdapat kolektor yang aktif. Kebijakan mulai SharingStarted.WhileSubscribed() akan membuat produser upstream tetap aktif meskipun terdapat subscriber yang aktif. Kebijakan mulai lainnya tersedia, misalnya SharingStarted.Eagerly untuk segera memulai produser atau SharingStarted.Lazily untuk mulai berbagi setelah pelanggan pertama muncul dan membuat flow terus aktif.

SharedFlow

Fungsi shareIn akan menampilkan SharedFlow, yakni alur panas yang memunculkan nilai ke semua konsumen yang mengumpulkan darinya. A SharedFlow adalah generalisasi StateFlow yang sangat dapat dikonfigurasi.

Anda dapat membuat SharedFlow tanpa menggunakan shareIn. Misalnya, Anda dapat menggunakan SharedFlow untuk mengirim tick ke bagian lain aplikasi agar semua konten di-refresh secara berkala pada waktu yang sama. Selain mengambil berita terbaru, Anda juga dapat me-refresh bagian informasi pengguna dengan koleksi topik favoritnya. Dalam cuplikan kode berikut, TickHandler menampilkan SharedFlow sehingga class lainnya tahu kapan harus me-refresh kontennya. Seperti halnya StateFlow, gunakan properti pendukung jenis MutableSharedFlow dalam class untuk mengirim item ke alur:

// 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() { ... }
    ...
}

Anda dapat menyesuaikan perilaku SharedFlow dengan cara berikut:

  • replay memungkinkan Anda mengirim ulang sejumlah nilai yang sebelumnya dimunculkan untuk subscriber baru.
  • onBufferOverflow memungkinkan Anda menentukan kebijakan tentang kapan buffer penuh berisi item akan dikirim. Nilai defaultnya adalah BufferOverflow.SUSPEND, yang membuat pemanggil ditangguhkan. Opsi lainnya adalah DROP_LATEST atau DROP_OLDEST.

MutableSharedFlow juga memiliki properti subscriptionCount yang berisi jumlah kolektor aktif, sehingga Anda dapat mengoptimalkan logika bisnis yang sesuai. MutableSharedFlow juga berisi fungsi resetReplayCache jika Anda tidak ingin memutar ulang informasi terbaru yang dikirim ke alur.

Referensi alur lainnya