第七章:状态管理实战与架构总结

第七章:状态管理实战与架构总结

本章通过完整的首页/个人中心/详情页数据流分析,巩固 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 教程学习! 🎉

上一章:第六章:UI 组件与 Material3 主题

相关推荐
颂love2 小时前
MySQL的执行流程
android·数据库·mysql
幽络源小助理5 小时前
全新UI 阅后即焚V2正式版系统源码_全开源_安全加密传输
安全·ui·开源·php源码
云起SAAS6 小时前
抖音小游戏源码 - 消消乐 | 含激励广告+成就系统 | 开箱即用商业级消除游戏模板
android·游戏·广告联盟·看激励广告联盟流量主·抖音小游戏源码 - 消消乐
大貔貅喝啤酒8 小时前
基于Windows下载安装Android Studio 3.3.2版本教程(2026详细图文版)
android·java·windows·android studio
程序员码歌8 小时前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能
2501_915106328 小时前
深入解析无源码iOS加固原理与方案,保护应用安全
android·安全·ios·小程序·uni-app·cocoa·iphone
ZC跨境爬虫10 小时前
跟着 MDN 学CSS day_2:(连接样式表与选择器的实战艺术)
java·前端·css·ui·html·媒体
ZC跨境爬虫11 小时前
跟着 MDN 学CSS day_1:(CSS 基石与色彩的艺术)
前端·javascript·css·ui·html
黄林晴12 小时前
重磅官宣:Android UI 开发正式进入 Compose-first 时代
android·google io