一、基础架构模式
// 典型 ViewModel 结构
class MyViewModel(
private val repository: DataRepository
) : ViewModel() {
// 对外暴露只读 StateFlow
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// 用户意图处理
fun fetchData() {
viewModelScope.launch {
repository.getDataFlow()
.onStart { _uiState.value = UiState.Loading }
.catch { e -> _uiState.value = UiState.Error(e.message) }
.collect { data ->
_uiState.value = UiState.Success(data)
}
}
}
}
// UI 状态密封类
sealed class UiState {
object Loading : UiState()
data class Success(val data: Data) : UiState()
data class Error(val message: String?) : UiState()
}
二、最佳实践示例
示例 1:自动数据刷新(推荐方案)
class AutoRefreshViewModel(
private val stockRepo: StockRepository
) : ViewModel() {
// 使用 stateIn 转换为热流
val stockData: StateFlow<StockData> = stockRepo.getRealTimeStockFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // 5秒超时停止
initialValue = StockData.EMPTY
)
}
// Activity/Fragment 中收集
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.stockData.collect { data ->
updateStockDisplay(data)
}
}
}
示例 2:搜索功能(防抖处理)
class SearchViewModel : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchResults: StateFlow<List<Result>> = _searchQuery
.debounce(300) // 防抖 300ms
.distinctUntilChanged()
.filter { it.length >= 3 }
.flatMapLatest { query ->
repository.search(query)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = emptyList()
)
fun onSearchQueryChanged(query: String) {
_searchQuery.value = query
}
}
三、生命周期管理
1. 使用 repeatOnLifecycle
// Fragment 中
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
}
}
}
2. 使用 Jetpack Compose 扩展
@Composable
fun StockScreen(viewModel: StockViewModel) {
val stockData by viewModel.stockData.collectAsStateWithLifecycle()
// 根据 stockData 更新 UI
}
四、结合 Retrofit 和 Room
1. 网络请求 + 本地缓存
class UserViewModel(
private val userRepo: UserRepository
) : ViewModel() {
val userData: StateFlow<User> = userRepo.getUserFlow()
.map { dbUser ->
if (shouldRefresh(dbUser)) {
// 自动触发网络更新
userRepo.refreshUser()
}
dbUser
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = User.EMPTY
)
}
2. 分页加载
class PagingViewModel(
private val pager: Pager<Int, Item>
) : ViewModel() {
val pagingData: Flow<PagingData<Item>> = pager
.flow
.cachedIn(viewModelScope) // 保持分页状态
}
五、异常处理最佳实践
1. 集中式错误处理
val dataFlow = repository.getDataFlow()
.catch { e ->
// 统一处理错误
emit(DataWrapper.Error(e))
}
.stateIn(...)
2. 错误重试机制
fun fetchWithRetry() {
viewModelScope.launch {
repository.getUnstableData()
.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000)
true
} else {
false
}
}
.collect(...)
}
}
六、性能优化技巧
1. 流共享配置
// 多个收集者共享同一个流
val sharedFlow = someColdFlow
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
replay = 1
)
2. 线程切换优化
val optimizedFlow = someFlow
.flowOn(Dispatchers.IO) // 上游在 IO 线程
.map { ... } // 下游在默认线程
七、依赖配置(build.gradle)
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
// 如需使用 StateFlow
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0"
}
八、关键要点总结
-
数据暴露原则:
- 优先使用
StateFlow
暴露 UI 状态 - 使用
stateIn
转换冷流为热流 - 通过
MutableStateFlow
控制数据更新
- 优先使用
-
生命周期管理:
- 使用
viewModelScope
自动取消协程 - 在 UI 层使用
repeatOnLifecycle
或collectAsStateWithLifecycle
- 使用
-
性能优化:
- 合理使用
SharingStarted
策略 - 正确使用
flowOn
切换线程上下文 - 对耗时操作使用
buffer
- 合理使用
-
架构建议:
- 遵循单一数据源原则
- 使用 Repository 模式抽象数据源
- 通过密封类包装 UI 状态
通过以上实践方案,可以构建出健壮、高效且易于维护的 Flow + ViewModel 架构。建议根据实际业务需求灵活调整,同时注意合理控制流的生命周期以避免资源泄漏。