👋 哈喽大家好,欢迎回到 Compose 零基础系列~前面七篇我们已经把组件、布局、样式、状态、列表、主题、页面跳转全都学完了,写个简单的多页面 APP 已经完全没问题。
但不知道你有没有发现一个问题:我们之前写的代码,UI 和数据逻辑混在一起。页面一多、逻辑一复杂,代码就会变得又乱又难维护,而且状态还容易丢失、内存泄漏。
在正式项目里,我们绝对不会这么写。Google 官方推荐的最佳实践是:Compose + ViewModel。
这一篇,我就用最通俗、最接地气的方式,带你真正进入项目级架构。不讲虚的理论,全程实战、代码可直接复制,学完你的代码风格会直接提升一个档次。
📌 本篇核心目标
- 搞懂 ViewModel 到底是干嘛的
- 学会为什么要用 ViewModel,而不是只用 remember
- 掌握 ViewModel 最基本用法:保存状态、分离逻辑
- 学会在 Compose 中使用 StateFlow / SharedFlow
- 实现网络请求模拟、列表加载、分页、加载状态
- 写出规范、可维护、接近企业级的代码结构
一、先说人话:ViewModel 到底有啥用?
你可以把它理解成:页面的 “数据管家” 。
它主要帮你干 4 件事:
- 状态保存屏幕旋转、语言切换、系统杀进程恢复时,remember 会丢数据,但 ViewModel 不会。
- UI 与逻辑分离页面只负责画 UI,所有业务逻辑、数据请求都丢给 ViewModel。
- 数据共享多个页面、多个组件可以共用同一个 ViewModel 的数据。
- 生命周期安全不会内存泄漏,生命周期比页面长,页面销毁它自动清理。
一句话总结:ViewModel = 存放数据 + 处理逻辑 + 保状态 + 防泄漏
二、第一步:添加依赖
在 app/build.gradle 里加上:
kotlin
// ViewModel Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
同步一下即可。
三、最简单示例:ViewModel 保存计数器
我们先写一个最经典的计数器,对比 remember 和 ViewModel 的区别。
3.1 创建 ViewModel
kotlin
class CountViewModel : ViewModel() {
// 定义状态
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count
// 加一
fun addCount() {
_count.value++
}
}
3.2 在 Compose 中使用
kotlin
@Composable
fun CountScreen(viewModel: CountViewModel = viewModel()) {
// 收集状态
val count by viewModel.count.collectAsStateWithLifecycle()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("当前计数:$count", fontSize = 24.sp)
Button(onClick = { viewModel.addCount() }) {
Text("点击 +1")
}
}
}
重点:
viewModel()系统自动创建、管理生命周期collectAsStateWithLifecycle()只有页面可见时才更新,省电、安全
你旋转屏幕,数字不会重置,这就是 ViewModel 的作用。
四、实战:完整列表 + 加载状态 + 模拟网络
这才是真实项目常用结构:
- 加载中
- 加载成功(展示列表)
- 加载失败
- 下拉刷新 / 点击重试
4.1 定义数据类
kotlin
data class Article(
val id: Int,
val title: String,
val desc: String
)
4.2 定义状态密封类(规范写法)
kotlin
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val msg: String) : UiState<Nothing>()
}
4.3 写 ViewModel(核心)
kotlin
class ListViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<Article>>>(UiState.Loading)
val uiState: StateFlow<UiState<List<Article>>> = _uiState
init {
loadData()
}
// 模拟网络请求
fun loadData() {
_uiState.value = UiState.Loading
viewModelScope.launch {
// 模拟延迟
delay(1000)
// 模拟数据
val data = List(10) {
Article(
id = it,
title = "文章标题 $it",
desc = "这是文章的详细描述内容 $it"
)
}
_uiState.value = UiState.Success(data)
}
}
}
4.4 UI 页面根据状态显示不同内容
kotlin
@Composable
fun ListScreen(viewModel: ListViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "文章列表",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 16.dp)
)
when (uiState) {
is UiState.Loading -> {
Box(Modifier.fillMaxSize()) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
is UiState.Success<*> -> {
val list = (uiState as UiState.Success<List<Article>>).data
LazyColumn(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(list) { article ->
ArticleItem(article)
}
}
}
is UiState.Error -> {
val msg = (uiState as UiState.Error).msg
Box(Modifier.fillMaxSize()) {
Text(
text = "加载失败:$msg",
modifier = Modifier.align(Alignment.Center),
color = Color.Red
)
}
}
}
}
}
@Composable
fun ArticleItem(article: Article) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium)
.padding(16.dp)
) {
Text(
text = article.title,
style = MaterialTheme.typography.titleLarge
)
Text(
text = article.desc,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 4.dp)
)
}
}
运行效果:
- 进入页面先显示加载圈
- 1 秒后展示列表
- 结构清晰、UI 和逻辑完全分离
五、为什么要用 StateFlow?为什么不用 remember?
很多新手会问:我用 remember 也能存状态,为啥要搞这么复杂?
我给你说真实开发理由:
- remember 只在当前 Composable 有效跳别的页面再回来,状态会重置。
- ViewModel 生命周期比 Compose 长旋转屏幕、切换夜间模式,数据不会丢。
- 逻辑可以复用、可测试网络请求、数据库操作写在 ViewModel,不跟 UI 耦合。
- 避免内存泄漏viewModelScope 自动跟着销毁,不会泄漏。
六、新手最容易踩的坑
- 把网络请求写在 @Composable 里每次重组都会发请求,疯狂重复调用。
- 直接用 mutableStateOf 代替 StateFlow小 demo 可以,项目不行,无法生命周期安全。
- 不使用 collectAsStateWithLifecycle后台也更新 UI,耗电、卡顿、崩溃风险。
- 一个 ViewModel 塞太多东西一个页面一个 ViewModel,不要全局大一统。
- 忘记处理 Loading / Error用户体验极差,正式项目必做状态封装。
七、本篇总结 + 下篇预告
本篇收获
- 理解 ViewModel = 数据管家,负责存状态、处理逻辑
- 学会 StateFlow + 状态收集
- 学会规范的 UiState 封装(加载 / 成功 / 失败)
- 写出了真正接近企业级的列表架构
- 代码结构清晰、可维护、可扩展
下篇预告
第九篇:Compose 动画基础与常用交互包括:
- 淡入淡出
- 位移动画
- 尺寸动画
- 点击涟漪、列表动画
- 简单炫酷的交互动画
八、系列更新进度
- 第一篇:零基础入门,环境搭建 + 第一个页面
- 第二篇:基础组件 + 三大布局
- 第三篇:Modifier 修饰符全解
- 第四篇:状态管理 remember、mutableStateOf
- 第五篇:LazyColumn、LazyRow 列表
- 第六篇:Material3 主题与深色模式
- 第七篇:Navigation 页面跳转与传参
- 第八篇:ViewModel + Compose 架构实战(当前)
- 第九篇:Compose 动画基础
- 第十篇:综合实战项目