一、布景

最近看了好多MVI的文章,原理大多都是参照google发布的 应用架构指南,可是完成方法有很多种,就想自己封装一套自己喜爱用的MVI架构,以供今后开发App运用。

说干就干,预备对标“玩Android”,利用供给的数据接口,建立一个自己习惯运用的一套App项目,项目地址:Github wanandroid。

二、MVI

先简单说一下MVI,从MVC到MVP到MVVM再到现在的MVI,google是为了一向解决痛点所以不断推出新的结构,详细的开展流程就不多做赘诉了,网上有好多,咱们能够选择性适合自己的。

应用架构指南中首要的便是两个架构图:

2.1 整体架构

Android App封装 ——架构(MVI + kotlin + Flow)

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承担

Android App封装 ——架构(MVI + kotlin + Flow)

2.3 MVI UI层的特色

MVI在UI层比较与MVVM的中心区别是它的两大特性:

  1. 仅有可信数据源
  2. 数据单向活动

Android App封装 ——架构(MVI + kotlin + Flow)

从图中能够看到,

  1. 数据从Data Layer -> ViewModel -> UI,数据是单向活动的。ViewModel将数据封装成UI State传输到UI elements中,而UI elements是不会传输数据到ViewModel的。
  2. UI elements上的一些点击或者用户事情,都会封装成events事情,发送给ViewModel

2.4 建立MVI要注意的点

了解了MVI的原理和特色后,咱们就要开端着手建立了,其中需求解决的有以下几点

  1. 界说UI Stateevents
  2. 构建UI State单向数据流UDF
  3. 构建事情流events
  4. UI State的订阅和发送

三、建立项目

3.1 界说UI Stateevents

咱们能够用interface先界说一个笼统的UI Stateeventseventintent是一个意思,都能够用来表示一次事情。

@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构建事情流

  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的结构基本就建立完毕了

3.5运转效果

Android App封装 ——架构(MVI + kotlin + Flow)

四、 总结

不管是MVC、MVP、MVVM还是MVI,首要便是View和Model之间的交互关系不同

  • MVI的中心是 数据的单向活动
  • MVI运用kotlin flow能够很方便的完成 响应式编程
  • MV整个View只依靠一个State刷新,这个State便是 仅有可信数据源

目前建立了基础结构,后续还会在此项目的基础上持续封装jetpack等愈加完善这个项目。

项目源码地址:Github wanandroid