StateFlow và SharedFlow

StateFlowSharedFlow là các API về flow (dòng dữ liệu) cho phép flow phát thông tin cập nhật trạng thái và phát giá trị cho nhiều thực thể tiêu thụ một cách tối ưu.

StateFlow

StateFlow là một flow quan sát được chứa thông tin trạng thái. Flow này phát thông tin trạng thái hiện tại và cập nhất mới cho các trình thu thập. Bạn cũng có thể đọc giá trị trạng thái hiện tại thông qua thuộc tính value. Để cập nhật trạng thái và gửi trạng thái này cho flow, hãy gán một giá trị mới cho thuộc tính value của lớp MutableStateFlow.

Trong Android, StateFlow là lựa chọn phù hợp cho những lớp cần duy trì trạng thái thay đổi được và quan sát được.

Theo ví dụ từ flow Kotlin, một StateFlow có thể được cung cấp từ LatestNewsViewModel để View có thể theo dõi thông tin cập nhật trạng thái giao diện người dùng, đồng thời giúp trạng thái màn hình duy trì sau khi thay đổi cấu hình.

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

Lớp chịu trách nhiệm cập nhật MutableStateFlow là thực thể sản xuất và tất cả các lớp thu thập từ StateFlow đều là thực thể tiêu thụ. Không giống như flow bị động (cold) được tạo bằng hàm tạo flow, StateFlow có trạng thái chủ động (hot): việc thu thập từ flow này không kích hoạt mã thực thể sản xuất. StateFlow luôn hoạt động và có trong bộ nhớ, luồng này chỉ đủ điều kiện để thu gom rác khi không có tham chiếu nào khác đến luồng này từ một gốc thu gom rác.

Khi thực thể tiêu thụ mới bắt đầu thu thập từ flow, thực thể này sẽ nhận được trạng thái mới nhất trong luồng dữ liệu và mọi trạng thái sau đó. Bạn có thể tìm thấy hành vi này trong các lớp khác quan sát được như LiveData.

View theo dõi StateFlow như với bất kỳ flow nào khác:

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

Để chuyển đổi bất kỳ flow nào sang StateFlow, hãy sử dụng toán tử trung gian stateIn.

StateFlow, Flow và LiveData

StateFlowLiveData có nhiều điểm tương đồng. Cả hai đều là lớp quan sát được chứa dữ liệu và cả hai đều hoạt động theo cùng một mẫu tương tự khi được sử dụng trong kiến trúc ứng dụng của bạn.

Tuy nhiên, hãy lưu ý rằng StateFlowLiveData hoạt động theo cách khác nhau:

  • StateFlow yêu cầu truyền một trạng thái ban đầu trong hàm khởi tạo trong khi LiveData thì không yêu cầu.
  • LiveData.observe() tự động huỷ đăng ký cho thực thể tiêu thụ khi khung hình chuyển sang trạng thái STOPPED, trong khi quá trình thu thập từ StateFlow hoặc bất kỳ flow nào khác sẽ không tự động ngừng thu thập. Để đạt được hành vi này, bạn cần thu thập flow từ khối Lifecycle.repeatOnLifecycle.

Chuyển flow bị động thành chủ động bằng cách sử dụng shareIn

StateFlow là flow chủ động – flow này luôn nằm trong bộ nhớ trong khoảng thời gian được thu thập hoặc trong khi mọi tham chiếu khác đến flow này tồn tại từ một gốc thu gom rác. Bạn có thể chuyển các flow bị động thành chủ động bằng cách sử dụng toán tử shareIn.

Lấy callbackFlow được tạo trong flow Kotlin làm ví dụ, thay vì để mỗi trình thu thập tạo một flow mới, bạn có thể chia sẻ dữ liệu truy xuất từ Firestore giữa các trình thu thập bằng cách sử dụng shareIn. Bạn cần chuyển những thông tin sau:

  • CoroutineScope dùng để chia sẻ flow. Phạm vi này sẽ tồn tại lâu hơn bất kỳ thực thể tiêu thụ nào để duy trì flow được chia sẻ trong khoảng thời gian cần thiết.
  • Số mục phát lại cho mỗi trình thu thập mới.
  • Chính sách về hành vi bắt đầu.
class NewsRemoteDataSource(...,
    private val externalScope: CoroutineScope,
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

Trong ví dụ này, flow latestNews sẽ phát lại mục được phát gần đây nhất cho trình thu thập mới và vẫn hoạt động miễn là externalScope còn hoạt động và còn có các trình thu thập đang hoạt động. Chính sách bắt đầu của SharingStarted.WhileSubscribed() giúp thực thể sản xuất của luồng ngược dòng (upstream) hoạt động trong khi có các trình theo dõi đang hoạt động. Chúng tôi cung cấp các chính sách bắt đầu khác, chẳng hạn như SharingStarted.Eagerly để bắt đầu thực thể sản xuất ngay lập tức hoặc SharingStarted.Lazily để bắt đầu chia sẻ sau khi trình theo dõi đầu tiên xuất hiện và giữ cho flow hoạt động vĩnh viễn.

SharedFlow

Hàm shareIn trả về một SharedFlow, một flow chủ động phát ra giá trị cho tất cả thực thể tiêu thụ thu thập từ flow đó. SharedFlow là mô hình tổng quát của StateFlow với khả năng định cấu hình linh hoạt.

Bạn có thể tạo SharedFlow mà không cần sử dụng shareIn. Ví dụ: bạn có thể sử dụng SharedFlow để gửi kim đánh dấu nhịp độ khung hình đến những phần khác của ứng dụng để tất cả nội dung được làm mới định kỳ cùng một lúc. Ngoài việc tìm nạp tin tức mới nhất, bạn cũng có thể làm mới mục thông tin người dùng bằng tập hợp các chủ đề mà họ yêu thích. Trong đoạn mã sau đây, TickHandler sẽ cung cấp một SharedFlow để các lớp khác biết thời điểm làm mới nội dung của mình. Tương tự như với StateFlow, hãy sử dụng thuộc tính dự phòng thuộc loại MutableSharedFlow trong một lớp để gửi các mục đến flow:

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

Bạn có thể tuỳ chỉnh hành vi của SharedFlow theo các cách sau:

  • replay cho phép bạn gửi lại một số giá trị đã phát trước đó cho các trình theo dõi mới.
  • onBufferOverflow cho phép bạn chỉ định chính sách cho thời điểm vùng đệm đã đầy các mục cần gửi. Giá trị mặc định là BufferOverflow.SUSPEND, khiến phương thức gọi bị tạm ngưng. Các tuỳ chọn khác là DROP_LATEST hoặc DROP_OLDEST.

MutableSharedFlow cũng có một thuộc tính subscriptionCount chứa số lượng trình thu thập đang hoạt động để bạn có thể tối ưu hoá logic nghiệp vụ của mình cho phù hợp. MutableSharedFlow cũng chứa một hàm resetReplayCache nếu bạn không muốn phát lại thông tin mới nhất được gửi đến flow này.

Các tài nguyên khác về flow