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

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

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

相关推荐
杉氧2 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
唐青枫5 小时前
别再把 inline 当性能开关:Kotlin 内联、noinline、crossinline 与 reified 实战详解
kotlin
Kapaseker6 小时前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
黄林晴6 小时前
AndroidX 官宣信号:Compose版WebView要来了!
kotlin
三少爷的鞋7 小时前
Android 现代架构不需要事件总线进阶篇
android
杉氧1 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏1 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧1 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄1 天前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭1 天前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android