[Android] MVI Development Architecture

Posted by xiuyuantech on 2023-05-12

MVI(Model-View-Intent)‌:一种基于响应式编程的架构模式,通过分离数据模型(Model)、视图(View)和用户意图(Intent)提升代码可维护性和可测试性‌。

前言

由于 MVVM 架构的状态与行为的管理混乱导致多人协同开发过程或者历史问题或架构问题导致直接操作 ViewModel 中的数据源导致可能的数据混乱。

所以 MVI 架构本质就是在 MVVM 架构的基础上进行了行为和数据上的约束,把数据流变成了单向流动,把状态集中管理形成唯一可信数据源。

架构思想

MVI 用数据流来理解界面刷新:界面是数据流的起点(生产者)也是终点(消费者),界面发出的数据叫意图,意图会用函数式编程的方式被变换为状态,最终状态通过响应式编程的方式流向界面,界面消费状态完成刷新。在这个流动的过程中,若保证了唯一可信数据源,就能实现单向数据流。

框架图如下:
Demo框架图

  • Intent:驱动model发生改变的意图,以UI中的事件最为常见;
  • Model:业务模型,包含数据和逻辑,是对应 客观实体 的 程序建模;
  • View:表现层的视图,以UI方式呈现Model的状态(以及事件),接受用户输入,转换为UI事件

特点

MVI与MVVM的核心区别是它的两大特性:

  1. 唯一可信数据源

    唯一可信数据源,是为了解决MVVM中View层使用大量LiveData,导致各种LiveData数据并行更新或者互相交互时会偶尔出现不可控逻辑,导致偶现一些的奇奇怪怪的Bug。

    MVI使用唯一可信的数据源UI State来避免这种问题。

  2. 数据单向流动。
    数据流向图

    从数据流向图中可以看到

    数据从Data Layer -> ViewModel -> UI,数据是单向流动的。ViewModel将数据封装成UI State传输到UI elements中,而UI elements是不会传输数据到ViewModel的。

    UI elements上的一些点击或者用户事件,都会封装成events事件,发送给ViewModel。

Demo

  1. 定义UI State、events
    用interface先定义一个抽象的UI State、events,event和intent是一个意思,都可以用来表示一次事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Keep
interface IUiState

@Keep
interface IUiIntent

//通过MainState将页面的不同状态封装起来,从而实现唯一可信数据源
data class MainState(val bannerUiState: BannerUiState, val detailUiState: DetailUiState) : IUiState

sealed class BannerUiState {
object INIT : BannerUiState()
data class SUCCESS(val models: List<BannerModel>) : BannerUiState()
}

sealed class DetailUiState {
object INIT : DetailUiState()
data class SUCCESS(val articles: ArticleModel) : DetailUiState()
}
  1. 构建事件流
    在ViewModel中使用 Channel/Flow 构建事件流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//基类
abstract class BaseViewModel<UiState : IUiState, UiIntent : IUiIntent> : ViewModel() {

private val _uiStateFlow = MutableStateFlow(initUiState())
val uiStateFlow: StateFlow<UiState> = _uiStateFlow

protected abstract fun initUiState(): UiState

protected fun sendUiState(copy: UiState.() -> UiState) {
_uiStateFlow.update { copy(_uiStateFlow.value) }
}

private val _uiIntentFlow: Channel<UiIntent> = Channel()
val uiIntentFlow: Flow<UiIntent> = _uiIntentFlow.receiveAsFlow()

fun sendUiIntent(uiIntent: UiIntent) {
viewModelScope.launch {
_uiIntentFlow.send(uiIntent)
}
}

init {
viewModelScope.launch {
uiIntentFlow.collect {
handleIntent(it)
}
}
}

protected abstract fun handleIntent(intent: IUiIntent)

}

//具体实现类
class MainViewModel : BaseViewModel<MainState, MainIntent>() {

private val mWanRepo = WanRepository()

override fun initUiState(): MainState {
return MainState(BannerUiState.INIT, DetailUiState.INIT)
}

override fun handleIntent(intent: IUiIntent) {
when (intent) {
MainIntent.GetBanner -> {
requestDataWithFlow(showLoading = true,
request = { mWanRepo.requestWanData() },
successCallback = { data -> sendUiState { copy(bannerUiState = BannerUiState.SUCCESS(data)) } },
failCallback = {})
}
is MainIntent.GetDetail -> {
requestDataWithFlow(showLoading = false,
request = { mWanRepo.requestRankData(intent.page) },
successCallback = { data -> sendUiState { copy(detailUiState = DetailUiState.SUCCESS(data)) } })
}
}
}

protected fun <T : Any> requestDataWithFlow(
showLoading: Boolean = true,
request: suspend () -> BaseData<T>,
successCallback: (T) -> Unit,
failCallback: suspend (String) -> Unit = { errMsg ->
//默认异常处理
},
) {
viewModelScope.launch {
val baseData: BaseData<T>
try {
baseData = request()
when (baseData.state) {
ReqState.Success -> {
sendLoadUiState(LoadUiState.ShowMainView)
baseData.data?.let { successCallback(it) }
}
ReqState.Error -> baseData.msg?.let { error(it) }
}
} catch (e: Exception) {
e.message?.let { failCallback(it) }
}
}
}
}
  1. UI State的订阅和发送

    在Activity/Fragment中订阅UI state和发送Intent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

abstract class BaseMVIActivity<VM : ViewModel,VB : ViewBinding>(val block: (LayoutInflater) -> VB): Activity() {

private var _binding: VB? = null
protected val binding: VB
get() = requireNotNull(_binding) { "The property of binding has been destroyed." }

protected lateinit var mViewModel: VM


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

initView(savedInstanceState)
}

override fun initView(savedInstanceState: Bundle?) {
_binding = block(layoutInflater)
setContentView(binding.root)
mViewModel = createViewModel()
}

/反射创建ViewModel
open protected fun createViewModel(): VM {
return ViewModelProvider(this).get(getVMCls(this))
}


override fun onDestroy() {
super.onDestroy()
_binding = null
}
}

class MainActivity : BaseMVIActivity() {

/* 在lifecycleScope中开启协程,collect uiStateFlow。
* 使用map 来做局部变量的更新
* 使用distinctUntilChanged来做数据防抖
*/

private fun registerEvent() {
lifecycleScope.launchWhenStarted {
mViewModel.uiStateFlow.map { it.bannerUiState }.distinctUntilChanged().collect { bannerUiState ->
when (bannerUiState) {
is BannerUiState.INIT -> {}
is BannerUiState.SUCCESS -> {
bannerAdapter.setList(bannerUiState.models)
}
}
}
}
lifecycleScope.launchWhenStarted {
mViewModel.uiStateFlow.map { it.detailUiState }.distinctUntilChanged().collect { detailUiState ->
when (detailUiState) {
is DetailUiState.INIT -> {}
is DetailUiState.SUCCESS -> {
articleAdapter.setList(detailUiState.articles.datas)
}
}

}
}
}

/* 调用sendUiIntent就可以发送Intent事件
* mViewModel.sendUiIntent(MainIntent.GetBanner)
* mViewModel.sendUiIntent(MainIntent.GetDetail(0))
*
* 调用 sendUiState 更新Ui State
* mViewModel.sendUiState(MainState.DetailUiState)
*/
}

MVI开源库推荐:

总结

不管是MVVM还是MVI,主要就是View和Model之间的交互关系不同

MVI的核心是 数据的单向流动
MVI使用kotlin flow可以很方便的实现 响应式编程
MV整个View只依赖一个State刷新,这个State就是 唯一可信数据源