赞
踩
Android MVI是一种用于构建Android应用程序的架构模式,其核心思想在于实现单向数据流和唯一可信数据源。在MVI架构中,应用程序的状态管理得到简化,并且用户界面与业务逻辑之间的交互更加清晰和规范。
MVI由Model、View和Intent三个核心组件组成:
Model:代表数据模型,负责存储应用程序的状态。它是唯一可信的数据源,意味着应用程序的所有状态都集中在这里管理,避免了状态不一致的问题。
View:即用户界面,负责展示Model中的状态,并响应用户的操作。当用户与界面进行交互时,会产生相应的Intent。
Intent:表示用户的意图或操作。它是View向Model传递信息的方式,告诉Model用户想要执行的操作或期望达到的状态。
在MVI架构中,数据流动是单向的:View产生Intent,Intent传递给Reducer(一个处理Intent并更新Model状态的函数),Reducer根据Intent生成新的Model状态,并将这个状态发送回View进行渲染。这种单向数据流有助于简化状态管理,并使得代码更加可预测和易于维护。
MVI架构还强调代码分层和清晰的责任划分。ViewModel无需关心View如何触发和更新,它只需要维护Intent和State。View与ViewModel的交互更加规范,使用Kotlin的密封类特性来封装Intent和State,使得代码更加规范、整洁和易读。
从图中可以看到,
- 数据从Data Layer -> ViewModel -> UI,数据是单向流动的。ViewModel将数据封装成
UI State
传输到UI elements中,而UI elements是不会传输数据到ViewModel的。- UI elements上的一些点击或者用户事件,都会封装成
events
事件,发送给ViewModel。- ViewModel更新状态:
- 在处理事件的过程中,
ViewModel
的内部状态(即UI State
)可能会发生变化。- 这些状态变化通常是基于用户操作和业务逻辑的结果。
- 通知Vew UI更新:
- 一旦
ViewModel
的状态发生变化,它需要通知UI
进行相应的更新。- 这通常是通过某种机制完成的,比如使用观察者模式(如 RxSwift、Kotlin Flow 等),或者通过数据绑定(如 Flutter 的数据流或 Jetpack Compose 的状态持有者)。
- 当
UI
接收到ViewModel
的状态更新时,它会根据新的状态重新渲染自己。
通过这样的流程,ViewModel
在应用程序中充当了一个中介角色,它接收 UI
的事件,处理这些事件并更新状态,然后通知 UI
进行相应的渲染。这种单向数据流的方式有助于保持代码的可维护性和可测试性,同时也有助于实现更清晰的组件间解耦。
搭建步骤:
- 定义
UI State
、events
- 构建
UI State
单向数据流UDF
- 构建事件流
events
UI State
的订阅和发送
UIState
、events
- import androidx.annotation.Keep
- @Keep
- interface IUiIntent
- import androidx.annotation.Keep
- @Keep
- interface IUiState
然后根据具体逻辑定义页面的UIState和UiIntent。
data class MainState(val bannerUiState: BannerUiState, val detailUiState: DetailUiState) : IUiState
- sealed class BannerUiState {
- object INIT : BannerUiState()
- data class SUCCESS(val models: List<Banner>) : BannerUiState()
- }
- sealed class DetailUiState {
- object INIT : DetailUiState()
- data class SUCCESS(val articles: Article) : DetailUiState()
- }
- sealed class MainIntent : IUiIntent {
- object GetBanner : MainIntent()
- data class GetDetail(val page: Int) : MainIntent()
- }
通过MainState
将页面的不同状态封装起来,从而实现唯一可信数据源
3.2 构建事件流
在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
构建事件流
有人好奇这里为啥用Channel,而不用SharedFlow或者StateFlow?
Channel就像一个队列一样,适合实现单个生产者和单个消费者之间的通信,而 SharedFlow 更适合实现多个观察者订阅同一数据源。而这里的Intent事件更像前者,各个协程生产出不同的Intent事件通过Channel发送给ViewModel,然后在ViewModel中集中处理消费。
_uiIntentFlow
用来传输IntentuiIntentFlow
,在子ViewModel中只用重写handlerIntent
方法就可以处理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()
- }
- }
- }
- }
在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)
- }
- }
-
- }
- }
- }
- }
直接调用sendUiIntent
就可以发送Intent事件
- button.setOnClickListener {
- mViewModel.sendUiIntent(MainIntent.GetBanner)
- mViewModel.sendUiIntent(MainIntent.GetDetail(0))
- }
调用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的框架基本就搭建完毕了
觉得我写的好的兄弟可以动动发财的小手帮我点个赞 谢谢!!!!!
源码链接地址:https://download.csdn.net/download/a546036242/88971170
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。