日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
MVI 架構(gòu)封裝:快速優(yōu)雅地實(shí)現(xiàn)網(wǎng)絡(luò)請求

前言  

網(wǎng)絡(luò)請求可以說是Android開發(fā)中最常見的需求之一,基本上每個(gè)頁面都需要發(fā)起幾個(gè)網(wǎng)絡(luò)請求。因此大家通常都會(huì)對(duì)網(wǎng)絡(luò)請求進(jìn)行一定的封裝,解決模板代碼過多,重復(fù)代碼,異常捕獲等一些問題。

成都創(chuàng)新互聯(lián)主營鎮(zhèn)坪網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件開發(fā),鎮(zhèn)坪h5成都小程序開發(fā)搭建,鎮(zhèn)坪網(wǎng)站營銷推廣歡迎鎮(zhèn)坪等地區(qū)企業(yè)咨詢

前面我們介紹了MVI架構(gòu)的主要原理與更佳實(shí)踐。相關(guān)文章如下所示:

MVVM進(jìn)階版:MVI架構(gòu)了解一下

我們這次一起來看下MVI架構(gòu)下如何對(duì)網(wǎng)絡(luò)請求進(jìn)行封裝,以及相對(duì)于MVVM架構(gòu)有什么優(yōu)勢。

本文主要包括以下內(nèi)容:

  1. MVVM架構(gòu)下的網(wǎng)絡(luò)請求封裝與問題
  2. MVI架構(gòu)下封裝網(wǎng)絡(luò)請求
  3. MVI架構(gòu)與Flow結(jié)合實(shí)現(xiàn)網(wǎng)絡(luò)請求

MVVM架構(gòu)下的網(wǎng)絡(luò)請求封裝與問題

相信大家都看過不少M(fèi)VVM架構(gòu)下的網(wǎng)絡(luò)請求封裝,一般是這樣寫的。

# MainViewModel
class MainViewModel {
private val _userLiveData = MutableStateLiveData()
val userLiveData : StateLiveData = _userLiveData
fun login(username: String, password: String) {
viewModelScope.launch {
_userLiveData.value = repository.login(username, password)
}
}
}
class MainActivity : AppCompatActivity() {
fun initViewModel(){
// 請求網(wǎng)絡(luò)
mViewModel.login("username", "password")
// 注冊監(jiān)聽
mViewModel.userLiveData.observeState(this) {
onLoading {
showLoading()
}
onSuccess {data ->
mBinding.tvContent.text = data.toString()
}
onError {
dismissLoading()
}
}
}
}

如上所示,就是最常見的MVVM架構(gòu)下網(wǎng)絡(luò)請求封裝,主要思路如是:

  1. 添加一個(gè)StateLiveData,一個(gè)LiveData支持多種狀態(tài),例如加載中,加載成功,加載失敗等
  2. 在頁面中監(jiān)聽StateLiveData,在頁面中處理onLoading,onSuccess,onError等邏輯

這種封裝的本質(zhì)其實(shí)就是將請求的回調(diào)邏輯處理遷移到View層了,這其實(shí)并不是我們想要的,我們的理想狀況應(yīng)該是邏輯盡量放在ViewModel中,View層只需要監(jiān)聽ViewModel層并更新UI。

既然這種封裝其實(shí)違背了不在View層寫邏輯的原則,那么為什么還有那么多人用呢?

本質(zhì)上是因?yàn)閂iewModel層與View層的通信成本比較高。

想象一下,如果我們不使用StateLiveData,針對(duì)每個(gè)請求就需要新建一個(gè)LiveData來表示請求狀態(tài),如果成功或失敗后需要彈Toast或者Dialog,或者頁面中有多個(gè)請求,就需要定義更多的LiveData, 同時(shí)為了保證對(duì)外暴露的LiveData不可變,每個(gè)狀態(tài)都需要定義兩遍LiveData。

這就是為什么這種封裝其實(shí)違背了不在View層寫邏輯但仍然流行的原因,因?yàn)樵贛VVM架構(gòu)中每處理一種狀態(tài),就需要添加兩個(gè)LiveData,成本較高,大多數(shù)人并不愿意支付這個(gè)成本。

而MVI架構(gòu)正解決了這個(gè)問題。

MVI架構(gòu)下封裝網(wǎng)絡(luò)請求

之前已經(jīng)介紹過了MVI架構(gòu),MVI架構(gòu)使用方面我們就不再多說,我們直接來看下MVI架構(gòu)下怎么發(fā)起一個(gè)簡單網(wǎng)絡(luò)請求。

簡單的網(wǎng)絡(luò)請求

class NetworkViewModel : ViewModel() {
/**
* 頁面請求,通常包括刷新頁面loading狀態(tài)等
*/
private fun pageRequest() {
viewModelScope.rxLaunch {
onRequest = {
_viewStates.setState { copy(pageStatus = PageStatus.Loading) }
delay(2000)
"頁面請求成功"
}
onSuccess = {
_viewStates.setState { copy(content = it, pageStatus = PageStatus.Success) }
_viewEvents.setEvent(NetworkViewEvent.ShowToast("請求成功"))
}
onError = {
_viewStates.setState { copy(pageStatus = PageStatus.Error(it)) }
}
}
}
}
# Activity層
class MainActivity : AppCompatActivity() {
private fun initViewModel() {
viewModel.viewStates.let { state ->
//監(jiān)聽網(wǎng)絡(luò)請求狀態(tài)
state.observeState(this, NetworkViewState::pageStatus) {
when (it) {
is PageStatus.Success -> state_layout.showContent()
is PageStatus.Loading -> state_layout.showLoading()
is PageStatus.Error -> state_layout.showError()
}
}
//監(jiān)聽頁面數(shù)據(jù)
state.observeState(this, NetworkViewState::content) {
tv_content.text = it
}
}
//監(jiān)聽一次性事件,如Toast,ShowDialog等
viewModel.viewEvents.observe(this) {
when (it) {
is NetworkViewEvent.ShowToast -> toast(it.message)
is NetworkViewEvent.ShowLoadingDialog -> showLoadingDialog()
is NetworkViewEvent.DismissLoadingDialog -> dismissLoadingDialog()
}
}
}
}

如上,代碼很簡單:

  1. 頁面的所有狀態(tài)都存儲(chǔ)在NetworkViewState中,后面如果需要添加狀態(tài)不需要添加LiveData,添加屬性即可,NetworkViewEvent中存儲(chǔ)了所有一次事件,同理
  2. ViewModel中發(fā)起網(wǎng)絡(luò)請求并監(jiān)聽網(wǎng)絡(luò)請求回調(diào),其中viewModelScope.rxLaunch是我們自定義的擴(kuò)展方法,后面會(huì)再介紹
  3. ViewModel中在請求的onRequest,onSuccess,onError時(shí)會(huì)通過_viewStates更新頁面,通過_viewEvents添加一次性事件,如Toast
  4. View層只需要監(jiān)聽ViewState與ViewEvent并更新UI,頁面的邏輯全都在ViewModel中寫

通過使用MVI架構(gòu),所有的邏輯都在ViewModel中處理,同時(shí)添加新狀態(tài)時(shí)不需要添加LiveData,降低了View與ViewModel的通信成本,解決了MVVM架構(gòu)下的一些問題。

局部網(wǎng)絡(luò)請求

我們頁面中通常會(huì)有一些局部網(wǎng)絡(luò)請求,例如點(diǎn)贊,收藏等,這些網(wǎng)絡(luò)請求不需要刷新整個(gè)頁面,只需要處理單個(gè)View的狀態(tài)或者彈出Toast。下面我們來看下MVI架構(gòu)下是如何實(shí)現(xiàn)的:

/**
* 頁面局部請求,例如點(diǎn)贊收藏等,通常需要彈dialog或toast
*/
private fun partRequest() {
viewModelScope.rxLaunch {
onRequest = {
_viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog)
delay(2000)
"點(diǎn)贊成功"
}
onSuccess = {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))
_viewStates.setState { copy(content = it) }
}
onError = {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
}
}
}

如上,針對(duì)局部網(wǎng)絡(luò)請求,我們也是通過_viewStates與_viewEvents更新UI,并不需要添加額外的LiveData,使用起來比較方便。

多數(shù)據(jù)源請求

頁面中通常也會(huì)有一些多數(shù)據(jù)源的請求,我們可以利用協(xié)程的async操作符處理。

/**
* 多數(shù)據(jù)源請求
*/
private fun multiSourceRequest() {
viewModelScope.rxLaunch {
onRequest = {
_viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog)
coroutineScope {
val source1 = async { source1() }
val source2 = async { source2() }
val result = source1.await() + "," + source2.await()
result
}
}
onSuccess = {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))
_viewStates.setState { copy(content = it) }
}
onError = {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
}
}
}

異常處理

我們的APP中通常需要一些通用的異常處理,我們可以封裝在rxLaunch擴(kuò)展方法中。

如上:

class CoroutineScopeHelper(private val coroutineScope: CoroutineScope) {
fun rxLaunch(init: LaunchBuilder.() -> Unit): Job {
val result = LaunchBuilder().apply(init)
val handler = NetworkExceptionHandler {
result.onError?.invoke(it)
}
return coroutineScope.launch(handler) {
val res: T = result.onRequest()
result.onSuccess?.invoke(res)
}
}
}

 如上:

  1. rxLaunch就是我們定義的擴(kuò)展方法,本質(zhì)就是將協(xié)程轉(zhuǎn)化為類RxJava的回調(diào)
  2. 通用的異常處理可寫在自定義的NetworkExceptionHandler中,如果請求錯(cuò)誤則會(huì)自動(dòng)處理
  3. 處理后的異常將傳遞到onError中,供我們進(jìn)一步處理

MVI架構(gòu)與Flow結(jié)合實(shí)現(xiàn)網(wǎng)絡(luò)請求

我們上面通過自定義擴(kuò)展函數(shù)實(shí)現(xiàn)了rxLaunch,其實(shí)是將協(xié)程轉(zhuǎn)化為類RXJava的寫法,但其實(shí)kotin協(xié)程已經(jīng)有了自己的RXJava : Flow。我們完全可以利用Flow來實(shí)現(xiàn)同樣的功能,不需要自己自定義。

簡單的網(wǎng)絡(luò)請求

/**
* 頁面請求,通常包括刷新頁面loading狀態(tài)等
*/
private fun pageRequest() {
viewModelScope.launch {
flow {
delay(2000)
emit("頁面請求成功")
}.onStart {
_viewStates.setState { copy(pageStatus = PageStatus.Loading) }
}.onEach {
_viewStates.setState { copy(content = it, pageStatus = PageStatus.Success) }
_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))
}.commonCatch {
_viewStates.setState { copy(pageStatus = PageStatus.Error(it)) }
}.collect()
}
}

  1. 在flow中發(fā)起網(wǎng)絡(luò)請求并將結(jié)果通過emit回調(diào)
  2. onStart是請求的開始,這里觸發(fā)Activity中的showLoading
  3. 在onEach中獲取flow中emit的結(jié)果,即成功回調(diào),在這里更新請求狀態(tài)與頁面數(shù)據(jù)
  4. 在commonCatch中捕獲異常
  5. 局部的網(wǎng)絡(luò)請求與這里類似,并且不需要添加額外的LiveData,這里就不綴述了

多數(shù)據(jù)源網(wǎng)絡(luò)請求

Flow中提供了多個(gè)操作符,可以將多個(gè)Flow的結(jié)果組合起來。

/**
* 多數(shù)據(jù)源請求
*/
private fun multiSourceRequest() {
viewModelScope.launch {
val flow1 = flow {
delay(1000)
emit("數(shù)據(jù)源1")
}
val flow2 = flow {
delay(2000)
emit("數(shù)據(jù)源2")
}
flow1.zip(flow2) { a, b ->
"$a,$b"
}.onStart {
_viewEvents.setEvent(NetworkViewEvent.ShowLoadingDialog)
}.onEach {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
_viewEvents.setEvent(NetworkViewEvent.ShowToast(it))
_viewStates.setState { copy(content = it) }
}.commonCatch {
_viewEvents.setEvent(NetworkViewEvent.DismissLoadingDialog)
}.collect()
}
}

如上,我們通過zip操作符組合兩個(gè)Flow,它將合并兩個(gè)Flow的結(jié)果并回調(diào),我們在onEach中將得到數(shù)據(jù)源1,數(shù)據(jù)源2。

異常處理

跟上面一樣,有時(shí)我們需要配置一些能用的異常處理,可以看到,我們在上面調(diào)用了commonCatch,這其實(shí)也是我們自定義的一個(gè)擴(kuò)展函數(shù)。

fun  Flow.commonCatch(action: suspend FlowCollector.(cause: Throwable) -> Unit): Flow {
return this.catch {
if (it is UnknownHostException || it is SocketTimeoutException) {
MyApp.get().toast("發(fā)生網(wǎng)絡(luò)錯(cuò)誤,請稍后重試")
} else {
MyApp.get().toast("請求失敗,請重試")
}
action(it)
}
}

如上所示,其實(shí)是對(duì)Flow.catch的一個(gè)封裝,讀者可以根據(jù)自己的需求封裝處理。

關(guān)于Repository

可以看到,我上面都沒有使用到Repository,都是直接在ViewModel層中處理。平常在項(xiàng)目開發(fā)中也可以發(fā)現(xiàn),一般的頁面并沒有寫Repository的需要,直接在ViewModel中處理即可。

但如果數(shù)據(jù)獲取比較復(fù)雜,比如同時(shí)從網(wǎng)絡(luò)與本地?cái)?shù)據(jù)獲取,或者需要復(fù)用網(wǎng)絡(luò)請求等時(shí),也可以添加一個(gè)Repository。我們可以通過Repository獲取數(shù)據(jù)后,再通過_viewState更新頁面狀態(tài),如下所示:

private fun fetchNews() {
viewModelScope.launch {
flow {
emit(repository.getMockApiResponse())
}.onStart {
_viewStates.setState { copy(fetchStatus = FetchStatus.Fetching) }
}.onEach {
_viewStates.setState { copy(fetchStatus = FetchStatus.Fetched, newsList = it.data)}
}.commonCatch {
_viewStates.setState { copy(fetchStatus = FetchStatus.Fetched) }
}.collect()
}
}

總結(jié)

在MVVM架構(gòu)下一般使用StateLiveData來進(jìn)行網(wǎng)絡(luò)架構(gòu)封裝,并在View層監(jiān)聽回調(diào),這種封裝方式的問題在于將網(wǎng)絡(luò)請求回調(diào)處理邏輯轉(zhuǎn)移到了View層,違背了盡量不在View層寫邏輯的原則。

但這種寫法流行的原因在于MVVM架構(gòu)下View與ViewModel交互成本較高,如果每個(gè)請求的回調(diào)都在ViewModel中處理,則需要定義很多LiveData,這是很多人不愿意做的。而MVI架構(gòu)解決了這個(gè)問題,將頁面所有狀態(tài)放在一個(gè)ViewState中,對(duì)外也只需要暴露一個(gè)LiveData。

MVI配合Flow或者自定義擴(kuò)展函數(shù),可以將頁面邏輯全部放在ViewModel中,View層只需要監(jiān)聽LiveData的屬性并刷新UI即可。當(dāng)頁面需要添加狀態(tài)時(shí),只需要給ViewState添加一個(gè)屬性而不是添加兩個(gè)LiveData,降低了View與ViewModel的交互成本。

如果你也覺得在View層監(jiān)聽網(wǎng)絡(luò)請求回調(diào)不是一個(gè)很好的設(shè)計(jì)的話,那么可以嘗試使用一下MVI架構(gòu)。


名稱欄目:MVI 架構(gòu)封裝:快速優(yōu)雅地實(shí)現(xiàn)網(wǎng)絡(luò)請求
標(biāo)題鏈接:http://www.dlmjj.cn/article/cccsjoi.html