85、MVVM架构实战:Repository模式、ViewModel、LiveData/Flow、数据绑定
MVVM 架构,说白了就是让代码各司其职。我最早做项目时,Activity 里塞满了网络请求、数据解析、UI 更新,一个文件上千行,改个需求得翻半天。后来用了 MVVM,才真正体会到什么叫「清爽」。今天咱们就把它拆开揉碎了讲清楚。
一、MVVM 的核心思想
MVVM 把界面、数据和业务逻辑分成了三层:
- Model:数据层,包括网络、数据库、本地文件等数据来源。
- View:视图层,就是 Activity、Fragment、Compose 这些 UI 组件。
- ViewModel:连接 View 和 Model 的桥梁,负责处理业务逻辑,管理 UI 状态。
你想想看,ViewModel 不持有 View 的引用,所以屏幕旋转时数据不会丢。这一点,我当年在重构一个电商项目时深有体会——以前用 MVP,每次旋转都要重新加载商品列表,用户体验很差。换成 MVVM 后,数据稳稳地待在 ViewModel 里,用户根本感觉不到旋转过。
关键原则:View 只负责展示和交互,ViewModel 只负责数据和逻辑,Model 只负责数据来源。谁也别越界。
二、Repository 模式——数据统一入口
Repository 是数据层的门面。View 和 ViewModel 不需要知道数据来自网络还是本地,它们只管调用 Repository 的方法就行。
我在项目中遇到过一个问题:多个页面都要获取用户信息,有的直接调 API,有的读缓存,结果数据不一致。后来统一用 Repository 管理,所有地方都走同一个入口,问题就解决了。
// UserRepository.kt
class UserRepository(
private val apiService: ApiService,
private val userDao: UserDao
) {
fun getUser(id: String): Flow<Result<User>> = flow {
// 先读缓存
val cached = userDao.getUser(id)
if (cached != null) {
emit(Result.success(cached))
}
// 再请求网络
try {
val response = apiService.fetchUser(id)
userDao.insertUser(response.data)
emit(Result.success(response.data))
} catch (e: Exception) {
// 网络失败时,如果缓存有数据就不报错
if (cached == null) {
emit(Result.failure(e))
}
}
}
}
我的习惯:Repository 里尽量用 Flow 返回数据,这样 ViewModel 可以灵活选择 collect 或 map。LiveData 虽然好用,但 Flow 在协程世界里更自然。
三、ViewModel——状态持有者
ViewModel 的生命周期比 Activity 长,所以适合存放 UI 状态。我见过有人把网络请求直接写在 ViewModel 里,其实不对——ViewModel 应该调用 Repository,而不是直接操作数据源。
// UserViewModel.kt
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
val userState: StateFlow<UiState<User>> = _userState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
repository.getUser(id).collect { result ->
_userState.value = result.fold(
onSuccess = { UiState.Success(it) },
onFailure = { UiState.Error(it.message ?: "未知错误") }
)
}
}
}
}
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
我曾经踩过的坑:在 ViewModel 里直接暴露 MutableStateFlow 给 View,结果 Fragment 里直接修改了状态,导致数据流混乱。记住:对外只暴露不可变的 StateFlow,修改只能通过 ViewModel 的方法。
四、LiveData vs Flow——怎么选?
| 特性 | LiveData | Flow |
|---|---|---|
| 生命周期感知 | 原生支持 | 需配合 repeatOnLifecycle |
| 协程支持 | 一般 | 原生支持 |
| 操作符丰富度 | 较少 | 非常丰富(map、flatMap、combine 等) |
| 适合场景 | 简单 UI 状态 | 复杂数据流、协程场景 |
我个人习惯:新项目全部用 Flow + StateFlow。LiveData 虽然简单,但 Flow 的灵活性和协程生态更强大。不过如果你在维护老项目,LiveData 完全够用,没必要强行迁移。
五、数据绑定——View 和 ViewModel 的桥梁
数据绑定有两种方式:传统的 DataBinding 和 Compose 的 State 驱动。这里我重点讲 DataBinding,因为很多项目还在用 XML。
<!-- activity_user.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.UserViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@{viewModel.userState.data?.name}"
android:visibility="@{viewModel.userState is Success ? View.VISIBLE : View.GONE}"
... />
<ProgressBar
android:visibility="@{viewModel.userState is Loading ? View.VISIBLE : View.GONE}"
... />
</LinearLayout>
</layout>
在 Activity 里绑定:
val binding = DataBindingUtil.setContentView<ActivityUserBinding>(this, R.layout.activity_user)
binding.viewModel = viewModel
binding.lifecycleOwner = this // 让 LiveData 感知生命周期
一个小技巧:在 XML 里用 BindingAdapter 自定义属性,比如加载图片、格式化时间,能让布局更干净。我经常把 Glide 加载封装成一个 BindingAdapter,一行代码搞定图片显示。
六、完整实战流程
咱们用一个用户列表的例子串起来:
- 定义数据源:Room 数据库 + Retrofit API
- 创建 Repository:提供 getUserList() 方法,返回 Flow<List<User>>
- ViewModel:持有 StateFlow<UiState<List<User>>>,调用 Repository 获取数据
- View:通过 DataBinding 或 collect 观察状态,自动更新 UI
你看,每一层职责都很清晰。View 不用管数据从哪来,ViewModel 不用管数据怎么展示,Repository 不用管数据怎么用。这就是 MVVM 的魅力。
核心总结:MVVM 不是银弹,但它能让你的代码结构清晰、易于测试、方便维护。我用了这么多年,最大的感受就是——改需求时不再心惊胆战了。
公众号:蓝海资料掘金营,微信deep3321