MVI 就像是 MVC 的一种衍生产物, 我们知道 MVP 也是 MVC 的衍生产物,MVVM 也是 MVP 的一种衍生
MVI 的愿景是能让 View 触发刷新的状态只有一个。
MVI 各层
MVI 将架构分成了三个部分:
Model: 与MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态
View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Model的变化实现界面刷新
Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求
MVI强调数据的单向流动,主要分为以下几步:
用户操作以Intent的形式通知Model
Model基于Intent更新State
View接收到State变化刷新UI。
数据永远在一个环形结构中单向流动,不能反向流动:
示例:
做一个玩Android的基本页面的框架搭建。效果如下:
在 Activity / Fragment 这种第一层级的视图中,定义触发 Intent 的逻辑,一般是通过点击事件等操作,和 MVVM 中的触发逻辑一样,不过这里要在协程中触发,并且使用 Channel 或其它 Flow 工具,因为这样做是一种响应式编程的逻辑。
mainVm.sendUiIntent(MainIntent.getDetail(0))
并且在 VieawModel 中定义数据流的开端:
abstract class BaseViewModel<UiState: IUiState, UiIntent: IUiIntent>: ViewModel() {
private val _uiIntentFlow: Channel<UiIntent> = Channel()
private val uiIntentFlow: Flow<UiIntent> = _uiIntentFlow.receiveAsFlow()
private val _uiStateFlow = MutableStateFlow(initUiState())
val uiStateFlow: StateFlow<UiState> = _uiStateFlow
private val _loadUiIntentFlow: Channel<LoadUiIntent> = Channel()
val loadUiIntentFlow: Flow<LoadUiIntent> = _loadUiIntentFlow.receiveAsFlow()
protected abstract fun initUiState(): UiState
init {
viewModelScope.launch {
uiIntentFlow.collect {
handleIntent(it)
}
}
}
fun sendUiIntent(uiIntent: UiIntent) {
viewModelScope.launch {
_uiIntentFlow.send(uiIntent)
}
}
/**
* 发送当前加载状态:Loading、Error、Normal
*/
private fun sendLoadUiIntent(loadUiIntent: LoadUiIntent) {
viewModelScope.launch {
_loadUiIntentFlow.send(loadUiIntent)
}
}
fun sendUiState(copy: UiState.() -> UiState) {
_uiStateFlow.update { copy(_uiStateFlow.value) }
}
protected abstract fun handleIntent(intent: IUiIntent)
protected fun <T: Any> requestDataWithFlow(
showLoading: Boolean = true,
request: suspend () -> BaseData<T>,
successCallBack: (T) -> Unit,
failCallBack: suspend (String) -> Unit = { errMsg->
sendLoadUiIntent(LoadUiIntent.Error(errMsg))
}
) {
viewModelScope.launch {
if (showLoading) {
sendLoadUiIntent(LoadUiIntent.Loading(true))
}
try {
val baseData = request.invoke()
if (baseData.isSuccess()) {
baseData.data?.let { successCallBack(it) }
} else {
baseData.errorMsg?.let { error(it) }
}
} catch (e: Exception) {
e.message?.let { failCallBack(it) }
} finally {
if (showLoading) {
sendLoadUiIntent(LoadUiIntent.Loading(false))
}
}
}
}
}
定义 UiState 作为 View 的唯一数据源
我们通过归纳新闻页的页面状态,可以分成 初始态、加载中、加载成功、加载失败 四个状态,那么我们将状态收归到 UiState 中去,使用 功能名+UiState 来命名:
@Keep
interface IUiState
@Keep
interface IUiIntent //event
sealed class LoadUiIntent {
data class Loading(var isShow: Boolean) : LoadUiIntent()
object ShowMainView : LoadUiIntent()
data class Error(val msg: String) : LoadUiIntent()
}
data class MainUiState(val detailUiState: DetailUiState): IUiState
sealed class DetailUiState {
object INIT: DetailUiState()
data class SUCCESS(val data: ArticleListData): DetailUiState()
}
ViewModel 持有 NewsUiState,并暴露出去,类似于 LiveData 那样子 :
Activity 依赖 ViewModel 持有的 UiState, 用于进行视图刷新:
private fun observe() {
lifecycleScope.launch {
mainVm.uiStateFlow.map { it.detailUiState }
.collect { detailUiState ->
when(detailUiState) {
is DetailUiState.INIT -> {
}
is DetailUiState.SUCCESS -> {
binding.recyclerView.visibility = View.VISIBLE
articleAdapter.setList(detailUiState.data.datas)
}
}
}
}
lifecycleScope.launch {
mainVm.loadUiIntentFlow.collect { state ->
when(state) {
is LoadUiIntent.Loading -> {
}
is LoadUiIntent.Error -> {
}
is LoadUiIntent.ShowMainView -> {
}
}
}
}
}
参考:
https://blog.csdn.net/rikkatheworld/article/details/126472940
https://github.com/HuJianChong/MyWanAndroid