Android App封裝 ——架構(MVI + kotlin + Flow)
theme: juejin highlight: darcula
一、背景
最近看了好多MVI的文章,原理大多都是參照google發佈的 應用架構指南,但是實現方式有很多種,就想自己封裝一套自己喜歡用的MVI架構,以供以後開發App使用。
説幹就幹,準備對標“玩Android”,利用提供的數據接口,搭建一個自己習慣使用的一套App項目,項目地址:Github wanandroid。
二、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. 唯一可信數據源 2. 數據單向流動。
從圖中可以看到,
1. 數據從Data Layer -> ViewModel -> UI,數據是單向流動的。ViewModel將數據封裝成UI State
傳輸到UI elements中,而UI elements是不會傳輸數據到ViewModel的。
2. UI elements上的一些點擊或者用户事件,都會封裝成events
事件,發送給ViewModel
2.4 搭建MVI要注意的點
瞭解了MVI的原理和特點後,我們就要開始着手搭建了,其中需要解決的有以下幾點
1. 定義UI State
、events
2. 構建UI State
單向數據流UDF
3. 構建事件流events
4. UI State
的訂閲和發送
三、搭建項目
3.1 定義UI State
、events
我們可以用interface先定義一個抽象的UI State
、events
,event
和intent
是一個意思,都可以用來表示一次事件。
```kotlin @Keep interface IUiState
@Keep
interface IUiIntent
然後根據具體邏輯定義頁面的UIState和UiIntent。
kotlin
data class MainState(val bannerUiState: BannerUiState, val detailUiState: DetailUiState) : IUiState
sealed class BannerUiState {
object INIT : BannerUiState()
data class SUCCESS(val models: List
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訂閲
```kotlin
abstract class BaseViewModel
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) }
}
}
kotlin
class MainViewModel : BaseViewModel
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
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
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
來做數據防抖
```kotlin
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事件
kotlin
button.setOnClickListener {
mViewModel.sendUiIntent(MainIntent.GetBanner)
mViewModel.sendUiIntent(MainIntent.GetDetail(0))
}
3.4.3 更新Ui State
調用sendUiState
發送Ui State更新
需要注意的是: 在UiState改變時,使用的是copy複製一份原來的UiState,然後修改變動的值。這是為了做到 “可信數據源”,在定義MainState
的時候,設置的就是val
,是為了避免多線程併發讀寫,導致線程安全的問題。
```kotlin
class MainViewModel : BaseViewModel
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等更加完善這個項目。
項目源碼地址:Github wanandroid