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>
}
在我的应用程序中,我有一个 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>
}