如何用 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 中无脑刷新数据

这三种方式完成功能都是没有任何问题的!!

普通思路的缺陷

但是在我看来, 这里有以下几个问题:

  1. 列表界面总是需要一个方式被通知数据更新了. 然后去刷新 UI 数据
    1. 比如被动收到 EventBus 或者广播的消息. 又或者利用界面的生命周期的方法
  2. 新增界面可能总是需要额外写一种通知的方式.
    1. 假设你需要其他地方能知道标签的数据有变更, 那你可能总是需要发通知等方式…

可以优化吗?

我们首先分析列表界面的刷新. 由于我们之前的方式是初始化获取数据, 后续 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 了.

这种方式, 我们可以很明显的发现以下几个优点:

  1. 标签列表界面, 不需要额外的去通知刷新了.
  2. 其他任何地方更新数据到数据库, 你的标签列表界面没销毁的话, 都能收到数据并且展示.
  3. 其他新增数据的地方也不用特定的发通知的事件了

至此, 你会发现我们写代码的方式, 其实已经变成了观察者模式. UI 的刷新也不再是 收到通知 + 获取新数据 的方式了.

而是, 新的数据会通过 LiveData 给你. 你只需要根据 LiveData 给你的数据显示 UI 即可

进阶?

我们上面的方式已经解决了问题. 但是也有一些小问题. 由于 LiveData 只是一个发射最近数据的一个观察者模式的实现类.

它并不具备对数据转化、过滤、重组、排序、分组等等的有用的功能.

所以在上面的 可观察的数据源 上, 我们的选择还有 RxJavaSubjectKotlinHot Flow 这种基于观察者模式实现的流

我这里是推荐 KotlinFlow, 这样, 我们的代码可以升级为:

// 相关接口
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 的数据你可以使用 RxJavaSubjectKotlinShareFlowStateFlow 去创建. 比如我简单的实现一个:

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 或者 RxJavaObservable 是冷的流还是热的流. 所以我强烈建议, 在平时开发的时候, 就用一个自定义的注解, 对返回的 FlowObservable 进行标记

比如:

// 相关接口
LabelService 
   // 表示返回的是一个热的流, 并且是 BEHAVIOR 模式, 也就是分发最近数据的模式
   @HotObservable(HotObservable.Pattern.BEHAVIOR)
   suspend subscribeAllLabel(): Flow<List<LabelDTO>>

希望看的有所收获!!

以上是关于如何用 MVVM 的思想落实到项目中的主要内容,如果未能解决你的问题,请参考以下文章

如何用 MVVM 的思想落实到项目中

如何用WPF实现一个最简单的Mvvm示例

如何用WPF实现一个最简单的Mvvm示例

如何连接 TextBox 的 TextChanged 事件和命令以便在 Silverlight 中使用 MVVM 模式

如何用快递100查快递单号

如何用ABP框架快速完成项目 - 如何正确使用ABP?