MVVM架构
# 概念
内部可修改,外部只读
Model
↓
ViewModel
↓
View
1
2
3
4
5
2
3
4
5
基本模式
class CounterViewModel : ViewModel() {
// 私有:ViewModel 内部可以修改
private val _count = MutableStateFlow(0)
// 对外:只能读取
val count = _count.asStateFlow()
}
// 流程
_count
↓
MutableStateFlow
↓
ViewModel 内部可以修改
count
↓
StateFlow
↓
Compose / 外部只能读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
业务方法
// ViewModel
class CounterViewModel : ViewModel() {
// model
private val _count = MutableStateFlow(0)
// model
val count = _count.asStateFlow()
// 加1
fun increment() {
_count.value += 1
}
// 重置为0
fun reset() {
_count.value = 0
}
}
// View
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val count by viewModel.count.collectAsState()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("数量: $count", fontSize = 40.sp)
Spacer(modifier = Modifier.height(10.dp))
Button(onClick = {
viewModel.increment()
}) {
Text("+1")
}
Button(onClick = {
viewModel.reset()
}) {
Text("重置")
}
}
}
// 流程
用户点击按钮
→ 调用 viewModel.increment
→ ViewModel 修改 _count.value
→ StateFlow 通知观察者
→ collectAsState 收到新值
→ Compose 重组(重新渲染)
→ 界面上的数字更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# AndroidViewModel
| 类型 | 是否需要 Application Context | 说明 |
|---|---|---|
ViewModel | 不需要 | 普通 ViewModel,不能持有 Context,适合纯业务逻辑,不要在这里引用 Activity / Application,避免内存泄漏 |
AndroidViewModel | 需要 | 继承自 ViewModel,构造参数携带 Application(全局上下文),可用于需要全局上下文的场景(如读取资源、数据库),不能传入 Activity Context |
class LoginViewModel(
application: Application
) : AndroidViewModel(application) {
}
1
2
3
4
5
2
3
4
5
# StateFlow
StateFlow是一个持有单个值的可观察数据流。值变化时通知所有观察者(UI 界面)自动更新。
// 创建:必须给初始值
private val _state = MutableStateFlow("初始值")
// 更新值:所有收集者会收到通知
_state.value = "新值"
// 读取当前值
println(_state.value) // 输出:新值
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
| 特点 | 说明 | 对比理解 |
|---|---|---|
| 总是有值 | 创建时必须给初始值 | 像一个变量,任何时候都有值 |
| 值相同时不通知 | 两次设同样的值不会触发更新 | 避免不必要的界面刷新 |
| 新收集者立即收到当前值 | 不需要等下一次变化 | 进入页面就能看到当前状态 |
| 适合表示 UI 状态 | loading、data、error 等 | 持续存在的状态 |
例子
class QuestionViewModel : ViewModel() {
// 模拟数据(第8讲学真实网络请求后替换)
private val mockData = listOf(...)
// 加载状态
private val _loading = MutableStateFlow(value = false)
val loading = _loading.asStateFlow()
// 数据列表
private val _questionList = MutableStateFlow<List<QuestionItem>>(value = emptyList())
val questionList = _questionList.asStateFlow()
// 发请求获取数据
fun loadData(){
viewModelScope.launch {
_loading.value = true
delay(duration = 2000.milliseconds)
_questionList.value = mockData
_loading.value = false
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# SharedFlow
| 特性 | StateFlow | SharedFlow |
|---|---|---|
| 值 | 持有当前值(持续状态) | 不持有(事件消费即消失) |
| 重复值 | 相同值不通知 | 每次都通知 |
| 初始值 | 必须有 | 不需要 |
| 新订阅者 | 立即收到当前值 | 不收到之前的事件 |
| 适用场景 | loading、data、error 等持续状态 | 导航跳转、Toast 提示等一次性事件 |
例子
class LoginViewModel : ViewModel() {
// 持续状态用 StateFlow:加载中
private val _loading = MutableStateFlow(false)
val loading = _loading.asStateFlow()
// 一次性事件用 SharedFlow:登录成功(通知UI跳转)
// 设置1个缓冲区。如果没有收集者时 emit 会挂起等待,设置缓冲区后事件不会丢失
private val _loginSuccess = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val loginSuccess = _loginSuccess.asSharedFlow()
// 一次性事件用 SharedFlow:错误消息(通知UI显示Toast)
private val _error = MutableSharedFlow<String>(extraBufferCapacity = 1)
val error = _error.asSharedFlow()
// 模拟用户数据(正式项目第9讲用 AuthManager 持久化)
private val validateUsers = mapOf(
"admin" to "123456",
"test" to "test123"
)
fun login(username: String, password: String) {
if (_loading.value) return
viewModelScope.launch {
_loading.value = true
delay(duration = 2000.milliseconds)
if (validateUsers[username] == password) {
// 登录成功 -> 发射成功事件
_loginSuccess.emit(value = Unit)
} else {
// 登录失败 -> 发射错误事件
_loginError.emit(value = "用户名或密码错误")
}
}
}
}
// UI层
@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
// 状态变量
var username by remember { mutableStateOf(value = "") }
var password by remember { mutableStateOf(value = "") }
val loading by viewModel.loading.collectAsState()
val context = LocalContext.current
// 收集登录成功事件
LaunchedEffect(key1 = Unit) {
viewModel.loginSuccess.collect {
Toast.makeText(context, text = "登录成功,token:$it", duration = Toast.LENGTH_SHORT).show()
}
}
// 收集登录失败事件
// 下面可以继续写LaunchedEffect收集loginError
// UI布局...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
_loading:StateFlow 用于持续 UI 状态,页面是否正在加载,UI 通过collectAsState订阅,用来展示加载动画。
_loginSuccess / _error:MutableSharedFlow 用于一次性事件:登录成功跳转、弹出错误 Toast。
# viewModelScope
viewModelScope 是 ViewModel专属的协程作用域,生命周期绑定 ViewModel。
class MyViewModel : ViewModel() {
fun loadData() {
// viewModelScope 绑定 ViewModel 生命周期
// ViewModel 销毁时自动取消所有协程
viewModelScope.launch {
delay(2000) // 模拟耗时操作
println("数据加载完成") // 在 Logcat 中查看
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
·
上次更新: 2026/10/07, 07:27:34