Android Flow + ViewModel 最佳实践教程

2,623 阅读2分钟

一、基础架构模式

// 典型 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"
}

八、关键要点总结

  1. 数据暴露原则

    • 优先使用 StateFlow 暴露 UI 状态
    • 使用 stateIn 转换冷流为热流
    • 通过 MutableStateFlow 控制数据更新
  2. 生命周期管理

    • 使用 viewModelScope 自动取消协程
    • 在 UI 层使用 repeatOnLifecycle 或 collectAsStateWithLifecycle
  3. 性能优化

    • 合理使用 SharingStarted 策略
    • 正确使用 flowOn 切换线程上下文
    • 对耗时操作使用 buffer
  4. 架构建议

    • 遵循单一数据源原则
    • 使用 Repository 模式抽象数据源
    • 通过密封类包装 UI 状态

通过以上实践方案,可以构建出健壮、高效且易于维护的 Flow + ViewModel 架构。建议根据实际业务需求灵活调整,同时注意合理控制流的生命周期以避免资源泄漏。

更多分享

  1. Android Flow 零基础到入门教程———基本概念和使用
  2. # Android Flow 零基础到入门教程———操作符详解!
  3. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)