ViewModel 中的 StateFlow 和 SharedFlow:使用建议以及单元测试
StateFlow 和 SharedFlow 都是 Kotlin Coroutines 中的数据流类型,用于在异步环境中传递数据。理解它们的区别并正确应用于 Android 开发中,对于构建响应式、可测试的 MVVM 架构至关重要。
核心概念简介
StateFlow:
- 一个状态容器式可观察数据流,可以向其收集器发出当前状态和新状态。
- 是热数据流(Hot Flow),始终持有最新值。
- 适用于 UI 状态管理,确保订阅者总能获取到最新的状态快照。
SharedFlow:
- SharedFlow 是 StateFlow 的可配置性极高的泛化数据流(StateFlow 继承于 SharedFlow)。
- 支持更灵活的重放策略(replay)和背压处理(backpressure)。
- 适用于事件通知、一次性操作或需要控制重播次数的场景。
为什么在 ViewModel 中暴露热流而不是冷流?
在 MVVM 模型中,ViewModel 中暴露出来的 StateFlow 或 SharedFlow 应该是 UI 层中唯一的可信数据来源。如果我们暴露出的是普通的冷流(Cold Flow),会导致每次有新的流收集者时就会触发一次 emit,造成资源浪费(例如重复的网络请求)。
如果 Repository 提供的只有简单的冷流,可以通过以下两种方式将其转换为热流:
- 手动转换:正常收集冷流,收集到一个数据就往另外构建的 StateFlow 或 SharedFlow 发送。
- 使用拓展函数:直接使用
stateIn或shareIn拓展函数转换成热流。这是官方推荐的方式。
使用 stateIn 示例
private const val DEFAULT_TIMEOUT = 500L
@HiltViewModel
class MyViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
// 将冷流转换为热流 StateFlow
val userFlow: StateFlow<UiState> = userRepository
.getUsers()
.asResult() // 此处返回 Flow<Result<User>>
.map { result ->
when (result) {
is Result.Loading -> UiState.Loading
is Result.Success -> UiState.Success(result.data)
is Result.Error -> UiState.Error(result.exception)
}
}
.stateIn(
scope = viewModelScope,
initialValue = UiState.Loading,
started = SharingStarted.WhileSubscribed(DEFAULT_TIMEOUT)
)
}

