手把手教你搭建 Android MVI架构: MVI + kotlin + Flow

一、什么是MVI

Android MVI是一种用于构建Android应用程序的架构模式,其核心思想在于实现单向数据流和唯一可信数据源。在MVI架构中,应用程序的状态管理得到简化,并且用户界面与业务逻辑之间的交互更加清晰和规范。

二、MVI组成

MVI由Model、View和Intent三个核心组件组成:

  1. Model:代表数据模型,负责存储应用程序的状态。它是唯一可信的数据源,意味着应用程序的所有状态都集中在这里管理,避免了状态不一致的问题。

  2. View:即用户界面,负责展示Model中的状态,并响应用户的操作。当用户与界面进行交互时,会产生相应的Intent。

  3. 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,使得代码更加规范、整洁和易读。

从图中可以看到,

  1. 数据从Data Layer -> ViewModel -> UI,数据是单向流动的。ViewModel将数据封装成UI State传输到UI elements中,而UI elements是不会传输数据到ViewModel的。
  2. UI elements上的一些点击或者用户事件,都会封装成events事件,发送给ViewModel。
  3. ViewModel更新状态:
    • 在处理事件的过程中,ViewModel 的内部状态(即 UI State)可能会发生变化。
    • 这些状态变化通常是基于用户操作和业务逻辑的结果。
  4. 通知Vew UI更新:
    • 一旦 ViewModel 的状态发生变化,它需要通知 UI 进行相应的更新。
    • 这通常是通过某种机制完成的,比如使用观察者模式(如 RxSwift、Kotlin Flow 等),或者通过数据绑定(如 Flutter 的数据流或 Jetpack Compose 的状态持有者)。
    • 当 UI 接收到 ViewModel 的状态更新时,它会根据新的状态重新渲染自己。

通过这样的流程,ViewModel 在应用程序中充当了一个中介角色,它接收 UI 的事件,处理这些事件并更新状态,然后通知 UI 进行相应的渲染。这种单向数据流的方式有助于保持代码的可维护性和可测试性,同时也有助于实现更清晰的组件间解耦。

三、MVI框架搭建

  搭建步骤:

  1. 定义UI Stateevents
  2. 构建UI State单向数据流UDF
  3. 构建事件流events
  4. UI State的订阅和发送
3.1 定义UIStateevents
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中集中处理消费。

  1. _uiIntentFlow用来传输Intent
  2. 在viewModelScope中开启协程监听uiIntentFlow,在子ViewModel中只用重写handlerIntent方法就可以处理Intent事件了
  3. 通过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的变化

  1. lifecycleScope中开启协程,collect uiStateFlow
  2. 使用map 来做局部变量的更新
  3. 使用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的框架基本就搭建完毕了

觉得我写的好的兄弟可以动动发财的小手帮我点个赞 谢谢!!!!!

源码链接地址:https://download.csdn.net/download/a546036242/88971170

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了搭建Android MVI框架,可以按照以下步骤进行: 1. 首先,需要在项目的build.gradle文件中添加以下依赖项: ```gradle dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding-swiperefreshlayout:2.2.0' implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0' implementation 'com.google.dagger:dagger:2.27' kapt 'com.google.dagger:dagger-compiler:2.27' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' implementation 'com.github.bumptech.glide:glide:4.12.0' kapt 'com.github.bumptech.glide:compiler:4.12.0' } ``` 2. 接下来,需要创建一个基础的MVI架构,包括以下几个部分: - Model:数据模型,用于存储应用程序的数据。 - View:用户界面,用于显示数据和接收用户输入。 - Intent:用户意图,用于描述用户的操作。 3. 在MVI架构中,还需要使用以下几个类: - Action:表示用户的操作,例如点击按钮或滑动屏幕。 - Result:表示操作的结果,例如从服务器获取的数据。 - ViewState:表示View的状态,例如显示数据或显示加载中的进度条。 4. 接下来,需要创建一个基础的MVI架构类,例如: ```kotlin abstract class MviViewModel<I : MviIntent, S : MviViewState, A : MviAction, R : MviResult> : ViewModel() { private val disposables = CompositeDisposable() abstract fun bindIntents() abstract fun actionFromIntent(intent: I): A abstract fun reducer(previousState: S, result: R): S abstract fun initialState(): S private val intentSubject: PublishSubject<I> = PublishSubject.create() private val stateObservable: Observable<S> by lazy { intentSubject .map { actionFromIntent(it) } .compose(actionProcessor) .scan(initialState(), ::reducer) .distinctUntilChanged() .replay(1) .autoConnect(0) } private val actionProcessor = ObservableTransformer<A, R> { actions -> actions.flatMap { action -> handleAction(action) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnError { throwable -> Log.e("MviViewModel", "Error handling action: $action", throwable) } } } private fun handleAction(action: A): Observable<R> { return Observable.empty() } fun processIntents(intents: Observable<I>) { intents.subscribe(intentSubject) .let(disposables::add) } fun states(): Observable<S> = stateObservable override fun onCleared() { super.onCleared() disposables.dispose() } } ``` 5. 最后,需要创建一个具体的ViewModel类,例如: ```kotlin class MyViewModel : MviViewModel<MyIntent, MyViewState, MyAction, MyResult>() { override fun bindIntents() { // TODO: Implement binding of intents to actions } override fun actionFromIntent(intent: MyIntent): MyAction { // TODO: Implement conversion of intent to action } override fun reducer(previousState: MyViewState, result: MyResult): MyViewState { // TODO: Implement state reducer } override fun initialState(): MyViewState { // TODO: Implement initial state } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值