如何用 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 的思想落实到项目中

如何通过单击适配器类中代码的项目中的删除按钮来删除列表视图中的项目后重新加载片段?

MVVM 模式中代码隐藏的实用使用

Qt C++ 项目中 Xamarin 项目中代码的可重用性

如何用快递100查快递单号

unity项目中代码中用到的类型总结