vitaviva
2023/03/16阅读:32主题:WeChat-Format
Android App 架构实战:Kotlin+MVI+Flow
作者:剑冲
https://juejin.cn/post/7177619630050000954
一、背景
最近看了好多MVI的文章,原理大多都是参照 google 发布的应用架构指南,但是实现方式有很多种,就想自己封装一套自己喜欢用的 MVI 架构,以供以后开发App使用。
说干就干,准备对标“玩Android”,利用提供的数据接口,搭建一个自己习惯使用的一套App项目。
-
项目地址:https://github.com/HuJianChong/MyWanAndroid -
架构指南:https://developer.android.google.cn/topic/architecture
二、MVI
先简单说一下 MVI,从 MVC 到 MVP 到 MVVM 再到现在的 MVI,google 是为了一直解决痛点所以不断推出新的框架,具体的发展流程就不多做赘诉了,网上有好多,我们可以选择性适合自己的。
应用架构指南中主要的就是两个架构图:
2.1 总体架构

Google推荐的是每个应用至少有两层:
-
UI Layer 界面层: 在屏幕上显示应用数据 -
Data Layer 数据层: 提供所需要的应用数据(通过网络、文件等) -
Domain Layer(optional)领域层/网域层 (可选):主要用于封装数据层的逻辑,方便与界面层的交互,可以根据User Case
图中主要的点在于各层之间的依赖关系是单向的,所以方便了各层之间的单元测试
2.2 UI层架构
UI简单来说就是拿到数据并展示,而数据是以state表示UI不同的状态传送给界面的,所以UI架构分为
-
UI elements 层:UI 元素,由 activity
、fragment
以及包含的控件组成 -
State holders 层: state
状态的持有者,这里一般是由viewModel
承担

2.3 MVI UI层的特点
MVI 在UI层相比与 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
2.4 搭建MVI要注意的点
了解了 MVI 的原理和特点后,我们就要开始着手搭建了,其中需要解决的有以下几点
-
定义UI State、events -
构建UI State单向数据流UDF -
构建事件流events -
UI State的订阅和发送
三、搭建项目
3.1 定义UI State、events
我们可以用 interface 先定义一个抽象的 UI State、events,event 和 intent 是一个意思,都可以用来表示一次事件。
@Keep
interface IUiState
@Keep
interface IUiIntent
然后根据具体逻辑定义页面的 UIState
和 UiIntent
。
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()
}
通过 MainState
将页面的不同状态封装起来,从而实现唯一可信数据源
3.2 构建单向数据流UDF
在 ViewModel 中使用 StateFlow 构建 UI State 流。
-
_uiStateFlow用来更新数据 -
uiStateFlow用来暴露给UI elements订阅
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) }
}
}
class MainViewModel : BaseViewModel<MainState, MainIntent>() {
override fun initUiState(): MainState {
return MainState(BannerUiState.INIT, DetailUiState.INIT)
}
}
3.3 构建事件流
在 ViewModel 中使用 Channel 构建事件流
-
_uiIntentFlow用来传输Intent -
在viewModelScope中开启协程监听uiIntentFlow,在子ViewModel中只用重写handlerIntent方法就可以处理Intent事件了 -
通过sendUiIntent就可以发送Intent事件了
abstract class BaseViewModel<UiState : IUiState, UiIntent : IUiIntent> : ViewModel() {
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>() {
override fun handleIntent(intent: IUiIntent) {
when (intent) {
MainIntent.GetBanner -> {
requestDataWithFlow()
}
is MainIntent.GetDetail -> {
requestDataWithFlow()
}
}
}
}
3.4 UI State的订阅和发送
3.4.1 订阅UI State
在 Activity 中订阅 UI state 的变化
-
在 lifecycleScope 中开启协程,collect uiStateFlow。 -
使用 map 来做局部变量的更新 -
使用 distinctUntilChanged
来做数据防抖
class MainActivity : BaseMVIActivity() {
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)
}
}
}
}
}
}
3.4.2 发送Intent
直接调用 sendUiIntent
就可以发送 Intent 事件
button.setOnClickListener {
mViewModel.sendUiIntent(MainIntent.GetBanner)
mViewModel.sendUiIntent(MainIntent.GetDetail(0))
}
3.4.3 更新Ui State
调用 sendUiState 发送Ui State更新
需要注意的是:在 UiState 改变时,使用的是 copy 复制一份原来的 UiState,然后修改变动的值。这是为了做到 可信数据源,在定义 MainState
的时候,设置的就是 val
,是为了避免多线程并发读写,导致线程安全的问题。
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)) } })
}
}
}
}
其中 requestDataWithFlow
是封装的一个网络请求的方法
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) }
}
}
}
至此一个 MVI 的框架基本就搭建完毕了
3.5运行效果
四、 总结
不管是 MVC、MVP、MVVM 还是MVI,主要就是 View 和 Model 之间的交互关系不同
-
MVI的核心是 数据的单向流动 -
MVI使用kotlin flow可以很方便的实现 响应式编程 -
MV整个View只依赖一个State刷新,这个State就是 唯一可信数据源
目前搭建了基础框架,后续还会在此项目的基础上继续封装jetpack等更加完善这个项目。
作者介绍