【Jetpack篇】協(xié)程+Retrofit網(wǎng)絡(luò)請(qǐng)求狀態(tài)封裝實(shí)戰(zhàn)(2)

一、前言

前幾天發(fā)布了一篇【Jetpack篇】協(xié)程+Retrofit網(wǎng)絡(luò)請(qǐng)求狀態(tài)封裝實(shí)戰(zhàn),在評(píng)論區(qū)里也收到了一些同僚的反饋:

image.png
image.png

......

具體問(wèn)題可以直接移步到上一篇評(píng)論區(qū)查看。

因?yàn)橛袔讉€(gè)問(wèn)題點(diǎn)還蠻重要,所以就上一篇文章新增了一些內(nèi)容,主要如下:

  • ? 新增局部狀態(tài)管理。如同一個(gè)頁(yè)面多個(gè)接口,可以分別管理狀態(tài)切換;
  • ? UI層新增Error,Empty,Success的Callback,開(kāi)發(fā)者可以自由選擇是否監(jiān)聽(tīng),處理業(yè)務(wù)邏輯更直觀、方便;
  • ? 結(jié)合第三方庫(kù)loadSir,統(tǒng)一切換UI。
  • ? 請(qǐng)求調(diào)用更加簡(jiǎn)單

好了,正文開(kāi)始。

二、局部請(qǐng)求狀態(tài)管理

很多時(shí)候app開(kāi)發(fā),存在同一個(gè)界面不同接口的情況,兩個(gè)接口同時(shí)請(qǐng)求,一個(gè)成功一個(gè)失敗,這個(gè)時(shí)候成功接口繼續(xù)顯示自己的頁(yè)面,失敗接口則顯示Error提示界面,如下圖

image.png

上一篇的封裝是將errorLiveData和loadingLiveData全局封裝在BaseFragment中,而他們的創(chuàng)建也是在BaseViewModel中,這樣就導(dǎo)致多個(gè)接口同時(shí)請(qǐng)求時(shí),如果某個(gè)接口發(fā)送錯(cuò)誤,就無(wú)法區(qū)分錯(cuò)誤來(lái)自哪里。

如果需要每個(gè)接口單獨(dú)管理自己的狀態(tài),那么就需要在ViewModel中創(chuàng)建多個(gè)erroeLiveData,這樣問(wèn)題是可以解決,但是會(huì)導(dǎo)致代碼非常冗余。既然需要每個(gè)接口管理不同狀態(tài),那就可以新建一個(gè)既包含請(qǐng)求返回結(jié)果又包含不同狀態(tài)值的LiveData,將之命名為StateLiveData

/**
 * MutableLiveData,用于將請(qǐng)求狀態(tài)分發(fā)給UI
 */
class StateLiveData<T> : MutableLiveData<BaseResp<T>>() {
}

而B(niǎo)aseResp中除了請(qǐng)求返回值的公共json外,還需要添加上不同的狀態(tài)值,我們將狀態(tài)值分為( STATE_CREATE,STATE_LOADING,STATE_SUCCESS,STATE_COMPLETED,STATE_EMPTY,STATE_FAILED, STATE_ERROR,STATE_UNKNOWN)幾種

enum class DataState {
    STATE_CREATE,//創(chuàng)建
    STATE_LOADING,//加載中
    STATE_SUCCESS,//成功
    STATE_COMPLETED,//完成
    STATE_EMPTY,//數(shù)據(jù)為null
    STATE_FAILED,//接口請(qǐng)求成功但是服務(wù)器返回error
    STATE_ERROR,//請(qǐng)求失敗
    STATE_UNKNOWN//未知
}

將DataState添加到BaseResp中,

/**
 * json返回的基本類(lèi)型
 */
class BaseResp<T>{
    var errorCode = -1
    var errorMsg: String? = null
    var data: T? = null
        private set
    var dataState: DataState? = null
    var error: Throwable? = null
    val isSuccess: Boolean
        get() = errorCode == 0
}

那StateLiveData該如何使用呢?

我們都知道數(shù)據(jù)請(qǐng)求會(huì)有不同的結(jié)果,成功,異常或者數(shù)據(jù)為null,那么就可以利用不同的結(jié)果,將相應(yīng)的狀態(tài)設(shè)置在BaseResp的DataState中。直接進(jìn)入到數(shù)據(jù)請(qǐng)求Repository層,對(duì)上篇異常處理做了改進(jìn)。

open class BaseRepository {
    /**
     * repo 請(qǐng)求數(shù)據(jù)的公共方法,
     * 在不同狀態(tài)下先設(shè)置 baseResp.dataState的值,最后將dataState 的狀態(tài)通知給UI
     */
    suspend fun <T : Any> executeResp(
        block: suspend () -> BaseResp<T>,
        stateLiveData: StateLiveData<T>
    ) {
        var baseResp = BaseResp<T>()
        try {
            baseResp.dataState = DataState.STATE_LOADING
            //開(kāi)始請(qǐng)求數(shù)據(jù)
            val invoke = block.invoke()
            //將結(jié)果復(fù)制給baseResp
            baseResp = invoke
            if (baseResp.errorCode == 0) {
                //請(qǐng)求成功,判斷數(shù)據(jù)是否為空,
                //因?yàn)閿?shù)據(jù)有多種類(lèi)型,需要自己設(shè)置類(lèi)型進(jìn)行判斷
                if (baseResp.data == null || baseResp.data is List<*> && (baseResp.data as List<*>).size == 0) {
                    //TODO: 數(shù)據(jù)為空,結(jié)構(gòu)變化時(shí)需要修改判空條件
                    baseResp.dataState = DataState.STATE_EMPTY
                } else {
                    //請(qǐng)求成功并且數(shù)據(jù)為空的情況下,為STATE_SUCCESS
                    baseResp.dataState = DataState.STATE_SUCCESS
                }

            } else {
                //服務(wù)器請(qǐng)求錯(cuò)誤
                baseResp.dataState = DataState.STATE_FAILED
            }
        } catch (e: Exception) {
            //非后臺(tái)返回錯(cuò)誤,捕獲到的異常
            baseResp.dataState = DataState.STATE_ERROR
            baseResp.error = e
        } finally {
            stateLiveData.postValue(baseResp)
        }
    }
}

executeResp()為數(shù)據(jù)請(qǐng)求的公共方法,該方法傳入了兩個(gè)參數(shù),第一個(gè)是將數(shù)據(jù)請(qǐng)求函數(shù)當(dāng)作參數(shù),第二個(gè)就是上面新建的StateLiveData

方法一開(kāi)始就新建了一個(gè)BaseResp<T>()對(duì)象,將DataState.STATE_LOADING狀態(tài)設(shè)置給BaseResp的dataState,接著開(kāi)始對(duì)數(shù)據(jù)請(qǐng)求進(jìn)行異常處理(具體可查看上一篇),如果code=0表示接口請(qǐng)求成功,否則表示接口請(qǐng)求成功,服務(wù)器返回錯(cuò)誤。在code=0時(shí),對(duì)返回?cái)?shù)據(jù)進(jìn)行判空處理,因?yàn)閿?shù)據(jù)有多種類(lèi)型,這里需要自己設(shè)置類(lèi)型進(jìn)行判斷,為空就將狀態(tài)設(shè)置為DataState.STATE_EMPTY,否則為

DataState.STATE_SUCCESS。如果拋出異常,則將狀態(tài)設(shè)置為DataState.STATE_ERROR,在請(qǐng)求結(jié)束后,利用stateLiveData將帶有狀態(tài)的baseResp分發(fā)給UI。

到這里,請(qǐng)求狀態(tài)都設(shè)置完成,接下來(lái)只需要根據(jù)不同狀態(tài),開(kāi)始進(jìn)行界面切換處理。

三、結(jié)合LoadSir界面切換

LoadSir是一個(gè)加載反饋?lái)?yè)管理框架,狀態(tài)頁(yè)自動(dòng)切換,具體使用在這里就不描述了,需要的可移步github查看。

LiveData接收數(shù)據(jù)變化時(shí),UI會(huì)先注冊(cè)一個(gè)接收事件的觀察者,接收到請(qǐng)求的數(shù)據(jù)后就進(jìn)行UI更新,第二節(jié)里將不同狀態(tài)也添加到了數(shù)據(jù)中,要想對(duì)狀態(tài)也進(jìn)行監(jiān)聽(tīng)的話,就需要對(duì)Observer進(jìn)行狀態(tài)處理。

/**
 * LiveData Observer的一個(gè)類(lèi),
 * 主要結(jié)合LoadSir,根據(jù)BaseResp里面的State分別加載不同的UI,如Loading,Error
 * 同時(shí)重寫(xiě)onChanged回調(diào),分為onDataChange,onDataEmpty,onError,
 * 開(kāi)發(fā)者可以在UI層,每個(gè)接口請(qǐng)求時(shí),直接創(chuàng)建IStateObserver,重寫(xiě)相應(yīng)callback。
 */
abstract class IStateObserver<T>(view: View?) : Observer<BaseResp<T>>, Callback.OnReloadListener {
    private var mLoadService: LoadService<Any>? = null

    init {
        if (view != null) {
            mLoadService = LoadSir.getDefault().register(view, this,
                Convertor<BaseResp<T>> { t ->
                    var resultCode: Class<out Callback> = SuccessCallback::class.java

                    when (t?.dataState) {

                        //數(shù)據(jù)剛開(kāi)始請(qǐng)求,loading
                        DataState.STATE_CREATE, DataState.STATE_LOADING -> resultCode =
                            LoadingCallback::class.java
                        //請(qǐng)求成功
                        DataState.STATE_SUCCESS -> resultCode = SuccessCallback::class.java
                        //數(shù)據(jù)為空
                        DataState.STATE_EMPTY -> resultCode =
                            EmptyCallback::class.java
                        DataState.STATE_FAILED ,DataState.STATE_ERROR -> {
                            val error: Throwable? = t.error
                            onError(error)
                            //可以根據(jù)不同的錯(cuò)誤類(lèi)型,設(shè)置錯(cuò)誤界面時(shí)的UI
                            if (error is HttpException) {
                                //網(wǎng)絡(luò)錯(cuò)誤
                            } else if (error is ConnectException) {
                                //無(wú)網(wǎng)絡(luò)連接
                            } else if (error is InterruptedIOException) {
                                //連接超時(shí)
                            } else if (error is JsonParseException
                                || error is JSONException
                                || error is ParseException
                            ) {
                                //解析錯(cuò)誤
                            } else {
                                //未知錯(cuò)誤
                            }
                            resultCode = ErrorCallback::class.java
                        }
                        DataState.STATE_COMPLETED, DataState.STATE_UNKNOWN -> {
                        }
                        else -> {
                        }
                    }
                    Log.d(TAG, "resultCode :$resultCode ")
                    resultCode
                })
        }

    }


    override fun onChanged(t: BaseResp<T>) {
        Log.d(TAG, "onChanged: ${t.dataState}")

        when (t.dataState) {
            DataState.STATE_SUCCESS -> {
                //請(qǐng)求成功,數(shù)據(jù)不為null
                onDataChange(t.data)
            }

            DataState.STATE_EMPTY -> {
                //數(shù)據(jù)為空
                onDataEmpty()
            }

            DataState.STATE_FAILED,DataState.STATE_ERROR->{
                //請(qǐng)求錯(cuò)誤
                t.error?.let { onError(it) }
            }
            else -> { }
        }

        //加載不同狀態(tài)界面
        Log.d(TAG, "onChanged: mLoadService $mLoadService")

        mLoadService?.showWithConvertor(t)

    }

    /**
     * 請(qǐng)求數(shù)據(jù)且數(shù)據(jù)不為空
     */
    open fun onDataChange(data: T?) {

    }

    /**
     * 請(qǐng)求成功,但數(shù)據(jù)為空
     */
    open fun onDataEmpty() {

    }

    /**
     * 請(qǐng)求錯(cuò)誤
     */
    open fun onError(e: Throwable?) {

    }
}

IStateObserverObserver接口的實(shí)現(xiàn)類(lèi),參數(shù)傳入了一個(gè)View,而這個(gè)View就是你所要替換的界面,這也就是同個(gè)界面,不同模塊顯示異常不同的關(guān)鍵所在。因?yàn)槭墙Y(jié)合Loadsir,首先需要初始化LoadService,再者通過(guò)dataState的狀態(tài)值,設(shè)置不同的Callback,例如Loading時(shí),設(shè)置為LoadingCallback,Error時(shí),設(shè)置為ErrorCallback,Empty時(shí)設(shè)置為EmptyCallback,設(shè)置完成后,在onChanged回調(diào)中統(tǒng)一調(diào)用showWithConvertor,也就是切換界面的操作。

而在onChange回調(diào)中,同樣根據(jù)狀態(tài)值,分別分發(fā)onDataChangeonDataEmpty,onError的通知。

到這里,完成了不同狀態(tài)界面切換和狀態(tài)通知的分發(fā)工作。

四、如何使用

上述基本上將整個(gè)流程封裝完成,使用起來(lái)也相對(duì)簡(jiǎn)便。

Repository層:

class ProjectRepo() : BaseRepository() {
      suspend fun loadProjectTree(stateLiveData: StateLiveData<List<ProjectTree>>) {
        executeResp({mService.loadProjectTree()},stateLiveData)
    }
}

直接就一行代碼,executeResp方法中傳入api的請(qǐng)求,以及StateLiveData。

ViewModel層:

class ProjectViewModel : BaseViewModel() {  
    val mProjectTreeLiveData = StateLiveData<List<ProjectTree>>()
        
     fun loadProjectTree() {
        viewModelScope.launch(Dispatchers.IO) {
            mRepo.loadProjectTree(mProjectTreeLiveData)
        }
    }

調(diào)用依舊是一行代碼,新建了一個(gè)StateLiveData,接著直接在viewModelScope作用域中調(diào)用Repository層的網(wǎng)絡(luò)請(qǐng)求,這里記得將StateLiveData作為參數(shù)傳進(jìn)去。

UI層:

class ProjectFragment : BaseFragment<FragmentProjectBinding, ProjectViewModel>() {
      override fun initData() {
          
        mViewModel?.loadProjectTree()
        mViewModel?.mProjectTreeLiveData?.observe(this,
            object : IStateObserver<List<ProjectTree>>(mBinding?.rvProjectAll) {
                override fun onDataChange(data: List<ProjectTree>?) {
                    super.onDataChange(data)
                    Log.d(TAG, "onDataChange: ")
                    data?.let { mAdapter.setData(it) }
                }

                override fun onReload(v: View?) {
                    Log.d(TAG, "onReload: ")
                    mViewModel?.loadProjectTree()
                }

                override fun onDataEmpty() {
                    super.onDataEmpty()
                    Log.d(TAG, "onDataEmpty: ")
                }

                override fun onError(e: Throwable?) {
                    super.onError(e)
                    showToast(e?.message!!)
                    Log.d(TAG, "onError: ${e?.printStackTrace()}")
                }
            })
      }
}

UI層利用ViewModel的StateLiveData注冊(cè)觀察者,與以往不同的是,mViewModel?.mProjectTreeLiveData?.observe()第二個(gè)參數(shù)替換為了IStateObserver,并且傳入了一個(gè)View,而這個(gè)View代表著的是當(dāng)請(qǐng)求異常時(shí),你所想替換的UI界面,同時(shí),也多了幾個(gè)回調(diào),

  • onDataChange:請(qǐng)求成功,數(shù)據(jù)不為空;
  • onReload:點(diǎn)擊重新請(qǐng)求;
  • onDataEmpty:數(shù)據(jù)為空時(shí);
  • onError:請(qǐng)求失敗

開(kāi)發(fā)者可以通過(guò)自己的業(yè)務(wù)需求,自由的選擇監(jiān)聽(tīng)。

我們來(lái)看看效果。

80cc44c18c36363d3d8d589814397a48.gif

五、最后

這次的整合彌補(bǔ)了一些細(xì)節(jié)問(wèn)題,更符合App開(kāi)發(fā)邏輯,當(dāng)然每個(gè)App的業(yè)務(wù)不同,這就要開(kāi)發(fā)者去定制化一些請(qǐng)求細(xì)節(jié),但是協(xié)程+Retrofit網(wǎng)絡(luò)請(qǐng)求的大致思路就是如此。
更多詳細(xì)的代碼可移步至github

源碼: 組件化+Jetpack+kotlin+mvvm

請(qǐng)結(jié)合【Jetpack篇】協(xié)程+Retrofit網(wǎng)絡(luò)請(qǐng)求狀態(tài)封裝實(shí)戰(zhàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容