StateFlow 设置值会丢弃一些事件,但更新不会

StateFlow setting value drops some events, but updating doesn't

在我的应用程序中,我有一个 UIState sealed class 来表示 UI 状态。

sealed class UIState<T> {
    class ShowLoading<T> : UIState<T>()
    class HideLoading<T> : UIState<T>()
    class ShowEmptyData<T> : UIState<T>()
    class ShowData<T>(val data: T) : UIState<T>()
    class ShowError<T>(val errorUIState: ErrorUIState) : UIState<T>()
}

因此,我的 viewmodel 代码是:

 someRequest.apply { response ->
     when (response) {
         is ApiResponse.Success -> {
             _uiStateFlow.value = (UIState.HideLoading()) // First, hide the loading
             // Do some work
             _uiStateFlow.value = (UIState.ShowData(data))
         }
         is ApiResponse.Error -> {
             _uiStateFlow.value = (UIState.ShowError(error))
         }
     }
 }

在这种情况下,大多数时候我的 hideLoading 状态不会收集,它会下降,因为 success/error 状态在 hideLoading 之后立即出现,而我的 UI 不会收集它。例如。如果我将延迟 success/error 状态设置为 100 毫秒,将从 UI 收集 hideLoading我使用 collect 而不是 collectLatest

但后来我发现,当我更改设置值部分以更新 {} 时,UI 会收集所有状态。

someRequest.apply { response ->
    when (response) {
        is ApiResponse.Success -> {
            _uiStateFlow.update { (UIState.HideLoading()) } // First, hide the loading
            // Do some work
            _uiStateFlow.update { (UIState.ShowData(data)) }
        }
        is ApiResponse.Error -> {
            _uiStateFlow.update { (UIState.ShowError(error)) }
        }
   }
}

那么 .value 和 update 之间有什么区别,为什么这个效果很好?谢谢。

P.S。我也用过 emit() 。在引擎盖下它与.value 相同,它只是一个挂起函数。

StateFlow documentation 中所述:

Updates to the value are always conflated.

Conflated 意味着如果值的发布速度比它们的收集速度快,那么收集器只会获得最新的结果。这允许始终将值发布到 StateFlow,而无需等待收集旧值。

至于为什么 update 允许加载状态通过,我怀疑这只是因为原子更新通常需要更长的时间,所以收集器通常会赢得比赛,在这种特定情况下您的特定测试设备。这不是确保收集器获得所有中间值的可靠解决方案。

我不明白你为什么首先需要 HideLoading 状态。您的 UI 可以简单地在收到要显示的数据时自动隐藏加载状态。从逻辑上讲,当数据返回时加载完成。

如果您确实需要此 HideLoading 状态,则应使用具有足够大 replay 值的 SharedFlow 以确保它不会被跳过。但这带来了其他问题,比如收集者可能会得到过时的数据来展示,因为它正在被重播给他们。

旁注,您的密封 class 可能应该是一个密封接口,因为它不包含任何状态,并且其不包含任何状态的子项可以是具有 [=14] 通用类型的 object =] 所以你不必为了使用它们而继续实例化它们,也不必在它们永远不持有该类型时不必要地指定泛型类型。由于其他的是数据包装器,因此它们也可能是 data classes。像这样:

sealed interface UIState<out T> {
    object ShowLoading : UIState<Nothing>
    // object HideLoading : UIState<Nothing>
    object ShowEmptyData : UIState<Nothing>
    data class ShowData<out T>(val data: T) : UIState<T>
    data class ShowError(val errorUIState: ErrorUIState) : UIState<Nothing>
}