如何用 MVVM 的思想落实到项目中
Posted 陈旭金-小金子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用 MVVM 的思想落实到项目中相关的知识,希望对你有一定的参考价值。
前言
MVVM 我相信大家也都听得多了. 但是我感觉还是有大部分人是理解不够到位的.下面呢, 我就用一个实际的例子来具体的讲述一下.
要实现的功能
- 一个标签列表
- 一个添加标签的界面
- 数据存储用了 Room 数据库
普通思路
// 相关接口
LabelService {
// 获取所有的标签
suspend getAllLabel(): List<LabelDTO>
}
// Room 数据库实现
LabelDao {
@Query("select * from label")
suspend getAllLabel(): List<LabelDO>
}
- 思路1
- 列表界面初始化的时候, 获取一遍数据.
- 点击新增按钮, 跳转到新增标签界面
- 点击完成, 插入数据库一条新的标签数据, 并通过 ActivityResult 返回一个标签对象
- 列表界面在 onActivityResult 中收到数据, 并完成新增数据的更新
- 思路2
- 列表界面初始化的时候, 获取一遍数据.
- 点击新增按钮, 跳转到新增标签界面
- 点击完成, 插入数据库一条新的标签数据, 通过发送广播或者 EventBus 的方式, 发出一个标签新增的事件
- 列表界面监听到事件, 完成界面的更新
- 思路3
- 列表界面初始化的时候, 获取一遍数据.
- 点击新增按钮, 跳转到新增标签界面
- 点击完成, 插入数据库一条新的标签数据. 不发出任何的通知.
- 列表界面在 onResult 中无脑刷新数据
这三种方式完成功能都是没有任何问题的!!
普通思路的缺陷
但是在我看来, 这里有以下几个问题:
- 列表界面总是需要一个方式被通知数据更新了. 然后去刷新 UI 数据
- 比如被动收到 EventBus 或者广播的消息. 又或者利用界面的生命周期的方法
- 新增界面可能总是需要额外写一种通知的方式.
- 假设你需要其他地方能知道标签的数据有变更, 那你可能总是需要发通知等方式…
可以优化吗?
我们首先分析列表界面的刷新. 由于我们之前的方式是初始化获取数据, 后续 get 到通知再刷新.
如果我们想简化这个流程. 我们需要一个则需要一个可观察的数据源的实现.
首先我们最熟悉的就是 LiveData 了. 而 LiveData 的模式是分发最近的一个数据. 这个就不展开说了. 这种模式非常适合 UI 数据的展示
而 Room 数据库正好也支持 LiveData, 所以我们的第一步优化就可变成如下的方式:
// 相关接口
LabelService {
// 获取所有的标签, 实现中会去监听 LabelDao 的数据做一些转化
suspend subscribeAllLabel(): LiveData<List<LabelDTO>>
}
// Room 数据库实现
LabelDao {
@Query("select * from label")
suspend subscribeAllLabel(): LiveData<List<LabelDO>>
}
class XxxActivity {
fun oncreate() {
// 这里监听 LiveData 更新 UI 代码就不写了
}
}
这时候, 我们的 UI 层就可以使用 LabelService.subscribeAllLabel() 的方法去订阅数据.
这时候我们在标签新增界面去新增一个标签到数据库, 数据库就会自动通过 LiveData 通知出去.
然后我们的标签列表界面就收到数据刷新 UI 了.
这种方式, 我们可以很明显的发现以下几个优点:
- 标签列表界面, 不需要额外的去通知刷新了.
- 其他任何地方更新数据到数据库, 你的标签列表界面没销毁的话, 都能收到数据并且展示.
- 其他新增数据的地方也不用特定的发通知的事件了
至此, 你会发现我们写代码的方式, 其实已经变成了观察者模式. UI 的刷新也不再是 收到通知 + 获取新数据 的方式了.
而是, 新的数据会通过 LiveData 给你. 你只需要根据 LiveData 给你的数据显示 UI 即可
进阶?
我们上面的方式已经解决了问题. 但是也有一些小问题. 由于 LiveData 只是一个发射最近数据的一个观察者模式的实现类.
它并不具备对数据转化、过滤、重组、排序、分组等等的有用的功能.
所以在上面的 可观察的数据源 上, 我们的选择还有 RxJava 的 Subject 和 Kotlin 的 Hot Flow 这种基于观察者模式实现的流
我这里是推荐 Kotlin 的 Flow, 这样, 我们的代码可以升级为:
// 相关接口
LabelService {
// 获取所有的标签, 实现中会去监听 LabelDao 的数据做一些转化
suspend subscribeAllLabel(): Flow<List<LabelDTO>>
}
// Room 数据库实现
LabelDao {
@Query("select * from label")
suspend subscribeAllLabel(): Flow<List<LabelDO>>
}
class XxxActivity {
fun oncreate() {
// 订阅 Flow
labelService
.subscribeAllLabel()
.onEach { labelList ->
// 更新 UI
}
.lanchIn(scope = lifecycleScope)
}
}
如果不使用 Room, 还能实现上述的功能吗?
可以的, 因为 Hot 的数据你可以使用 RxJava 的 Subject 和 Kotlin 的 ShareFlow 和 StateFlow 去创建. 比如我简单的实现一个:
interface TestService {
@HotObservable(HotObservable.Pattern.BEHAVIOR)
val labelFlow: Flow<List<LabelDTO>>
// 插入数据
suspend fun insert(target: LabelDTO)
}
class TestServiceImpl: TestService {
override val labelFlow: Flow<LabelDTO> = MutableSharedFlow<LabelDTO>(
replay = 1,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.SUSPEND
)
override suspend fun insert(target: LabelDTO) {
labelFlow.emit(
labelFlow.value.toMutableList().add(target)
)
}
}
写在最后
由于我们没法区分 Flow 或者 RxJava 的 Observable 是冷的流还是热的流. 所以我强烈建议, 在平时开发的时候, 就用一个自定义的注解, 对返回的 Flow 和 Observable 进行标记
比如:
// 相关接口
LabelService {
// 表示返回的是一个热的流, 并且是 BEHAVIOR 模式, 也就是分发最近数据的模式
@HotObservable(HotObservable.Pattern.BEHAVIOR)
suspend subscribeAllLabel(): Flow<List<LabelDTO>>
}
希望看的有所收获!!
以上是关于如何用 MVVM 的思想落实到项目中的主要内容,如果未能解决你的问题,请参考以下文章