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

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

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

相关推荐
传奇开心果编程1 分钟前
【ArkUI 练中学】第14课:应用性能优化实战
学习·ui·华为·harmonyos
2401_833269302 小时前
RecyclerView多布局详解
android
v_34836087622 小时前
Android_8 zygote启动分析
android
消失的旧时光-19432 小时前
Android 系统层扫盲 11:JNI 到底是什么?Java / Kotlin 是怎么调用 C/C++ 的?
android·binder·zygote·system_server·aops
mmsx3 小时前
Android 地图几万要素卡成幻灯片?顶点缓冲批上传与显存复用
android·opengl·地图·栅格
传奇开心果编程3 小时前
【Flutter入门练中学】第1课:从零开始
学习·flutter·ui
北京自在科技13 小时前
谷歌 Find Hub 迎来新版本更新
android·安卓
web守墓人14 小时前
【goed/ui】自定义组件设计思想篇
linux·windows·ui·golang
惟恋惜18 小时前
第 3 章 · 列表、滚动与 Lazy 深入:告别 RecyclerView 思维
ui·android jetpack
古法安卓19 小时前
Android-休眠唤醒后onLocationChanged没有数据问题排查
android·java·android studio