28、ViewModel + RecyclerView:结合 MVVM 架构管理列表数据
说实话,前面几章我们把 RecyclerView 的用法讲得差不多了。但有个问题一直悬着——数据到底该放哪?
我早期做项目时,习惯把列表数据直接塞在 Fragment 或 Activity 里。结果呢?屏幕一旋转,数据全没了。要么就得靠 onSaveInstanceState 手动存,麻烦得要命。后来接触了 MVVM 架构,才意识到:数据的管理,应该交给 ViewModel。
这一章,我们就来聊聊怎么把 ViewModel 和 RecyclerView 结合起来。说白了,就是让列表数据有个靠谱的“家”。
为什么需要 ViewModel?
先问个问题:你的列表数据,真的需要 ViewModel 吗?
如果你的列表只是展示几个静态字符串,那确实没必要。但真实项目里,列表数据往往来自网络请求、本地数据库,或者需要用户交互修改。这时候,ViewModel 的价值就体现出来了。
- 生命周期感知:ViewModel 在 Activity/Fragment 重建时自动保留数据
- 数据与视图分离:列表数据不再依赖 UI 组件,方便测试
- 配合 LiveData/Flow:数据变化时自动通知 RecyclerView 刷新
我在项目中遇到过最典型的场景:用户在一个列表页筛选条件,切到别的 App 再回来,筛选状态全丢了。用 ViewModel 之后,这个问题再没出现过。
MVVM 架构下的列表数据流
咱们先画个图,看看 ViewModel 在列表数据流里到底扮演什么角色。
这个图你看懂了吗?核心就一句话:ViewModel 持有数据,View 观察数据,数据变了 RecyclerView 自动刷新。
实战:写一个 ViewModel 管理列表
光说不练假把式。咱们直接写代码。
假设我们要做一个待办事项列表。每个事项有标题、描述和完成状态。
1. 定义数据模型
data class TodoItem(
val id: Long,
val title: String,
val description: String,
val isCompleted: Boolean = false
)
2. 创建 ViewModel
class TodoViewModel : ViewModel() {
// 使用 MutableLiveData 管理列表
private val _todoList = MutableLiveData<List<TodoItem>>()
val todoList: LiveData<List<TodoItem>> = _todoList
// 初始化一些数据
init {
loadTodos()
}
private fun loadTodos() {
// 模拟从网络或数据库加载
val items = listOf(
TodoItem(1, "学习 RecyclerView", "掌握基础用法", false),
TodoItem(2, "学习 ViewModel", "理解生命周期", true),
TodoItem(3, "完成项目", "结合 MVVM 实战", false)
)
_todoList.value = items
}
// 添加新事项
fun addTodo(title: String, description: String) {
val currentList = _todoList.value?.toMutableList() ?: mutableListOf()
val newId = (currentList.maxOfOrNull { it.id } ?: 0) + 1
currentList.add(TodoItem(newId, title, description))
_todoList.value = currentList
}
// 切换完成状态
fun toggleCompleted(id: Long) {
val currentList = _todoList.value?.toMutableList() ?: return
val index = currentList.indexOfFirst { it.id == id }
if (index != -1) {
currentList[index] = currentList[index].copy(
isCompleted = !currentList[index].isCompleted
)
_todoList.value = currentList
}
}
}
3. 在 Fragment 中绑定 ViewModel 和 RecyclerView
class TodoListFragment : Fragment() {
private lateinit var binding: FragmentTodoListBinding
private lateinit var adapter: TodoAdapter
private val viewModel: TodoViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentTodoListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeViewModel()
}
private fun setupRecyclerView() {
adapter = TodoAdapter { todoItem ->
// 点击切换完成状态
viewModel.toggleCompleted(todoItem.id)
}
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@TodoListFragment.adapter
}
}
private fun observeViewModel() {
viewModel.todoList.observe(viewLifecycleOwner) { items ->
// 数据变化时,自动刷新列表
adapter.submitList(items)
}
}
}
你看,Fragment 的代码变得非常干净。它只负责两件事:设置 RecyclerView,观察 ViewModel 的数据变化。至于数据怎么来、怎么改,那是 ViewModel 的事。
避坑指南:我踩过的几个坑
- 直接修改 LiveData 的 value:比如 _todoList.value?.add(item),这样不会触发数据更新。必须重新赋值一个新的 List 对象。
- 在 ViewModel 里持有 Context:ViewModel 生命周期比 Activity 长,持有 Context 会导致内存泄漏。用 AndroidViewModel 时要小心。
- 忘记使用 viewLifecycleOwner:在 Fragment 里观察 LiveData 时,一定要用 viewLifecycleOwner,而不是 this。否则可能引发多次观察。
进阶:使用 StateFlow 替代 LiveData
如果你用的是 Kotlin 协程,我个人更推荐用 StateFlow。它和 LiveData 功能类似,但更灵活。
class TodoViewModel : ViewModel() {
private val _todoList = MutableStateFlow<List<TodoItem>>(emptyList())
val todoList: StateFlow<List<TodoItem>> = _todoList.asStateFlow()
init {
loadTodos()
}
private fun loadTodos() {
// 模拟加载
_todoList.value = listOf(
TodoItem(1, "学习 RecyclerView", "掌握基础用法", false),
TodoItem(2, "学习 ViewModel", "理解生命周期", true)
)
}
fun addTodo(title: String, description: String) {
val currentList = _todoList.value.toMutableList()
currentList.add(TodoItem(
id = (currentList.maxOfOrNull { it.id } ?: 0) + 1,
title = title,
description = description
))
_todoList.value = currentList
}
}
在 Fragment 里观察 StateFlow 也很简单:
lifecycleScope.launch {
viewModel.todoList.collect { items ->
adapter.submitList(items)
}
}
核心要点总结:
- ViewModel 负责管理列表数据,不依赖 UI 组件
- 通过 LiveData 或 StateFlow 将数据暴露给 View 层
- View 层只做观察和展示,不直接操作数据
- 数据变化时,调用 adapter.submitList() 自动刷新
- 注意 LiveData 的赋值方式,必须重新创建 List 对象
嗯,这一章的内容就这些。ViewModel + RecyclerView 的组合,说白了就是让数据管理变得有章可循。你想想看,以前写列表时那些乱七八糟的状态管理,现在是不是清晰多了?
下一章我们会聊聊更高级的话题——分页加载。到时候你会看到,ViewModel 在管理大量列表数据时,能发挥更大的作用。
公众号:蓝海资料掘金营,微信deep3321