項(xiàng)目搭建經(jīng)歷記錄
- Android App封裝 ——架構(gòu)(MVI + kotlin + Flow)
- Android App封裝 —— ViewBinding
- Android App封裝 —— DI框架 Hilt?Koin阅仔?
- Android App封裝 —— 實(shí)現(xiàn)自己的EventBus
一藕甩、背景
最近看了好多MVI的文章锥累,原理大多都是參照google發(fā)布的 應(yīng)用架構(gòu)指南土铺,但是實(shí)現(xiàn)方式有很多種,就想自己封裝一套自己喜歡用的MVI架構(gòu)帆焕,以供以后開(kāi)發(fā)App使用帖旨。
說(shuō)干就干,準(zhǔn)備對(duì)標(biāo)“玩Android”彬呻,利用提供的數(shù)據(jù)接口衣陶,搭建一個(gè)自己習(xí)慣使用的一套App項(xiàng)目,項(xiàng)目地址:Github wanandroid闸氮。
二剪况、MVI
先簡(jiǎn)單說(shuō)一下MVI,從MVC到MVP到MVVM再到現(xiàn)在的MVI蒲跨,google是為了一直解決痛點(diǎn)所以不斷推出新的框架译断,具體的發(fā)展流程就不多做贅訴了,網(wǎng)上有好多或悲,我們可以選擇性適合自己的孙咪。
應(yīng)用架構(gòu)指南中主要的就是兩個(gè)架構(gòu)圖:
2.1 總體架構(gòu)
[圖片上傳失敗...(image-ad7268-1673343374642)]
Google推薦的是每個(gè)應(yīng)用至少有兩層:
- UI Layer 界面層: 在屏幕上顯示應(yīng)用數(shù)據(jù)
- Data Layer 數(shù)據(jù)層: 提供所需要的應(yīng)用數(shù)據(jù)(通過(guò)網(wǎng)絡(luò)、文件等)
- Domain Layer(optional)領(lǐng)域?qū)?網(wǎng)域?qū)?(可選):主要用于封裝數(shù)據(jù)層的邏輯巡语,方便與界面層的交互翎蹈,可以根據(jù)User Case
圖中主要的點(diǎn)在于各層之間的依賴關(guān)系是單向的,所以方便了各層之間的單元測(cè)試
2.2 UI層架構(gòu)
UI簡(jiǎn)單來(lái)說(shuō)就是拿到數(shù)據(jù)并展示捌臊,而數(shù)據(jù)是以state表示UI不同的狀態(tài)傳送給界面的杨蛋,所以UI架構(gòu)分為
- UI elements層:UI元素,由
activity、fragment
以及包含的控件組成 - State holders層: state狀態(tài)的持有者逞力,這里一般是由
viewModel
承擔(dān)
[圖片上傳失敗...(image-60cfc6-1673343374643)]
2.3 MVI UI層的特點(diǎn)
MVI在UI層相比與MVVM的核心區(qū)別是它的兩大特性:
- 唯一可信數(shù)據(jù)源
- 數(shù)據(jù)單向流動(dòng)曙寡。
[圖片上傳失敗...(image-8de122-1673343374643)]
從圖中可以看到,
- 數(shù)據(jù)從Data Layer -> ViewModel -> UI寇荧,數(shù)據(jù)是單向流動(dòng)的举庶。ViewModel將數(shù)據(jù)封裝成
UI State
傳輸?shù)経I elements中,而UI elements是不會(huì)傳輸數(shù)據(jù)到ViewModel的揩抡。 - UI elements上的一些點(diǎn)擊或者用戶事件户侥,都會(huì)封裝成
events
事件,發(fā)送給ViewModel
2.4 搭建MVI要注意的點(diǎn)
了解了MVI的原理和特點(diǎn)后峦嗤,我們就要開(kāi)始著手搭建了蕊唐,其中需要解決的有以下幾點(diǎn)
- 定義
UI State
、events
- 構(gòu)建
UI State
單向數(shù)據(jù)流UDF
- 構(gòu)建事件流
events
-
UI State
的訂閱和發(fā)送
三烁设、搭建項(xiàng)目
3.1 定義UI State
替梨、events
我們可以用interface先定義一個(gè)抽象的UI State
、events
装黑,event
和intent
是一個(gè)意思副瀑,都可以用來(lái)表示一次事件。
@Keep
interface IUiState
@Keep
interface IUiIntent
然后根據(jù)具體邏輯定義頁(yè)面的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()
}
通過(guò)MainState
將頁(yè)面的不同狀態(tài)封裝起來(lái)糠睡,從而實(shí)現(xiàn)唯一可信數(shù)據(jù)源
3.2 構(gòu)建單向數(shù)據(jù)流UDF
在ViewModel中使用StateFlow
構(gòu)建UI State流。
-
_uiStateFlow
用來(lái)更新數(shù)據(jù) -
uiStateFlow
用來(lái)暴露給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 構(gòu)建事件流
在ViewModel中使用 Channel構(gòu)建事件流
-
_uiIntentFlow
用來(lái)傳輸Intent - 在viewModelScope中開(kāi)啟協(xié)程監(jiān)聽(tīng)
uiIntentFlow
疚颊,在子ViewModel中只用重寫handlerIntent
方法就可以處理Intent事件了 - 通過(guò)sendUiIntent就可以發(fā)送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
的訂閱和發(fā)送
3.4.1 訂閱UI State
在Activity中訂閱UI state的變化
- 在
lifecycleScope
中開(kāi)啟協(xié)程狈孔,collect
uiStateFlow
。 - 使用
map
來(lái)做局部變量的更新 - 使用
distinctUntilChanged
來(lái)做數(shù)據(jù)防抖
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 發(fā)送Intent
直接調(diào)用sendUiIntent
就可以發(fā)送Intent事件
button.setOnClickListener {
mViewModel.sendUiIntent(MainIntent.GetBanner)
mViewModel.sendUiIntent(MainIntent.GetDetail(0))
}
3.4.3 更新Ui State
調(diào)用sendUiState
發(fā)送Ui State更新
需要注意的是: 在UiState改變時(shí)串稀,使用的是copy復(fù)制一份原來(lái)的UiState除抛,然后修改變動(dòng)的值。這是為了做到 “可信數(shù)據(jù)源”母截,在定義MainState
的時(shí)候,設(shè)置的就是val
橄教,是為了避免多線程并發(fā)讀寫清寇,導(dǎo)致線程安全的問(wèn)題。
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
是封裝的一個(gè)網(wǎng)絡(luò)請(qǐng)求的方法
protected fun <T : Any> requestDataWithFlow(
showLoading: Boolean = true,
request: suspend () -> BaseData<T>,
successCallback: (T) -> Unit,
failCallback: suspend (String) -> Unit = { errMsg ->
//默認(rèn)異常處理
},
) {
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) }
}
}
}
至此一個(gè)MVI的框架基本就搭建完畢了
四护蝶、 總結(jié)
不管是MVC华烟、MVP、MVVM還是MVI持灰,主要就是View和Model之間的交互關(guān)系不同
- MVI的核心是 數(shù)據(jù)的單向流動(dòng)
- MVI使用kotlin flow可以很方便的實(shí)現(xiàn) 響應(yīng)式編程
- MV整個(gè)View只依賴一個(gè)State刷新盔夜,這個(gè)State就是 唯一可信數(shù)據(jù)源
目前搭建了基礎(chǔ)框架,后續(xù)還會(huì)在此項(xiàng)目的基礎(chǔ)上繼續(xù)封裝jetpack等更加完善這個(gè)項(xiàng)目。
項(xiàng)目源碼地址:Github wanandroid