第七章:状态管理实战与架构总结
本章通过完整的首页/个人中心/详情页数据流分析,巩固 MVVM + Repository + UiState 架构模式。
7.1 首页完整数据流
kotlin
// 1. HomePage 调用 viewModel
@Composable
fun HomePage(navController: NavController) {
val viewModel: HomeViewModel = viewModel()
val uiState by viewModel.uiState.collectAsState()
HomePageContent(
uiState = uiState,
onArticleClick = { article ->
navController.navigate(AppRoutes.detailRoute(article.id))
},
onSearchChange = viewModel::updateSearchKeyword,
onSortConfirm = viewModel::sortByTitle,
onRetry = viewModel::loadData,
)
}
// 2. ViewModel 管理状态
class HomeViewModel : ViewModel() {
private var sourceList = mutableListOf<ArticleBean>()
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
init { loadData() }
fun loadData() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
try {
val result = ArticleRepository.getArticleList()
sourceList = result.toMutableList()
_uiState.value = HomeUiState.Success(articles = result)
} catch (e: Exception) {
_uiState.value = HomeUiState.Error(e.message ?: "加载失败")
}
}
}
fun updateSearchKeyword(keyword: String) {
val filtered = if (keyword.isBlank()) sourceList
else sourceList.filter { it.title.contains(keyword, ignoreCase = true) }
_uiState.value = HomeUiState.Success(articles = filtered, searchKeyword = keyword)
}
fun sortByTitle() {
val current = _uiState.value as? HomeUiState.Success ?: return
_uiState.update {
HomeUiState.Success(
articles = current.articles.sortedBy { it.title },
searchKeyword = current.searchKeyword,
)
}
}
}
// 3. Composable 渲染不同状态
@Composable
fun HomePageContent(uiState: HomeUiState, ...) {
when (uiState) {
is HomeUiState.Loading -> Box(contentAlignment = Alignment.Center) { CircularProgressIndicator() }
is HomeUiState.Error -> Column { Text(uiState.message); Button(onRetry) { Text("重试") } }
is HomeUiState.Success -> HomeSuccessContent(uiState)
}
}
7.2 个人中心完整数据流
kotlin
// ViewModel
class ProfileViewModel : ViewModel() {
private val _uiState = MutableStateFlow<ProfileUiState>(ProfileUiState.Loading)
val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow()
init { loadProfile() }
fun loadProfile() {
viewModelScope.launch {
_uiState.value = ProfileUiState.Loading
try {
val user = ProfileRepository.getProfile() // 本地示例数据,300ms 延迟
_uiState.value = ProfileUiState.Success(user = user)
} catch (e: Exception) {
_uiState.value = ProfileUiState.Error(e.message ?: "加载失败")
}
}
}
fun setNotificationEnabled(enabled: Boolean) {
_uiState.update { state ->
if (state is ProfileUiState.Success) state.copy(notificationEnabled = enabled)
else state
}
}
}
// UiState 定义
sealed interface ProfileUiState {
data object Loading : ProfileUiState
data class Success(val user: ProfileUser, val notificationEnabled: Boolean = true) : ProfileUiState
data class Error(val message: String) : ProfileUiState
}
特点:
- 通知开关状态保存在
Success.notificationEnabled - 调用
setNotificationEnabled直接更新 UI 状态,无需网络请求
7.3 详情页完整数据流
kotlin
// DetailPage 绑定 ViewModel
@Composable
fun DetailPage(navController: NavController, articleId: Int) {
val viewModel: DetailViewModel = viewModel(
key = "detail_$articleId",
factory = DetailViewModelFactory(articleId),
)
val uiState by viewModel.uiState.collectAsState()
DetailPageContent(uiState = uiState, onBackClick = { navController.popBackStack() })
}
// ViewModel
class DetailViewModel(private val articleId: Int) : ViewModel() {
private val _uiState = MutableStateFlow<DetailUiState>(DetailUiState.Loading)
val uiState: StateFlow<DetailUiState> = _uiState.asStateFlow()
init { loadArticle() }
fun loadArticle() {
viewModelScope.launch {
_uiState.value = DetailUiState.Loading
val article = ArticleRepository.getArticleById(articleId) // 从缓存读取
_uiState.value = if (article != null) DetailUiState.Success(article)
else DetailUiState.Error("未找到该商品")
}
}
}
// Factory(因为 ViewModel 有构造参数)
class DetailViewModelFactory(private val articleId: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DetailViewModel(articleId) as T
}
}
7.4 三页面对比总结
| 维度 | 首页 | 个人中心 | 详情页 |
|---|---|---|---|
| UiState | HomeUiState |
ProfileUiState |
DetailUiState |
| 数据来源 | API 网络请求 | 本地示例数据 | 内存缓存 |
| 搜索/过滤 | 支持 | 不支持 | 不支持 |
| 排序 | 支持 | 不支持 | 不支持 |
| 通知开关 | 不支持 | 支持 | 不支持 |
| ViewModel 参数 | 无 | 无 | articleId |
| 工厂模式 | 否 | 否 | 是 |
7.5 架构统一性
每个页面的代码结构完全一致:
XxxPage(navController)
→ viewModel() 获取 ViewModel
→ collectAsState() 监听 UI 状态
→ XxxPageContent(uiState, callbacks)
→ when(uiState) { Loading / Success / Error }
Repository 职责清晰:
ArticleRepository.getArticleList()→ 网络 + 缓存ArticleRepository.getArticleById(id)→ 缓存查询ProfileRepository.getProfile()→ 本地示例数据
7.6 课后练习建议
| 练习 | 难度 | 说明 |
|---|---|---|
| 添加商品收藏功能 | 简单 | 本地状态,参考通知开关 |
| 实现下拉刷新 | 中等 | 配合 PullToRefreshBox |
| 添加商品分类筛选 | 中等 | 首页增加筛选按钮 |
| 详情页独立请求 API | 困难 | 改为 GET products/{id} |
7.7 后续可改进方向
| 方向 | 说明 |
|---|---|
| 详情页独立 API | 当前依赖列表缓存,可改为 GET products/{id} |
| 统一错误处理 | Repository 返回 Result<T>,统一错误类型 |
| 依赖注入 | 引入 Hilt/Koin 替代 object 单例,便于测试 |
| 本地持久化 | 添加 Room 数据库,支持离线缓存 |
| 单元素测试 | Mock Repository 测试 ViewModel |
7.8 总结
- 三个页面遵循统一的 MVVM + sealed UiState 模式
- ViewModel 管理状态,Composable 只负责渲染
- Repository 统一数据来源,网络/本地/缓存分层
- 状态变化通过 StateFlow 观察,collectAsState() 转为 Compose 状态
- UI 与业务逻辑完全解耦,便于测试与维护
恭喜完成 MyFirstCompose 教程学习! 🎉