美文网首页Android学习学习之鸿蒙&Android
【Jetpack篇】协程+Retrofit网络请求状态封装实战(

【Jetpack篇】协程+Retrofit网络请求状态封装实战(

作者: 付十一v | 来源:发表于2021-05-13 21:01 被阅读0次

    一、前言

    前几天发布了一篇【Jetpack篇】协程+Retrofit网络请求状态封装实战,在评论区里也收到了一些同僚的反馈:

    image.png image.png

    ......

    具体问题可以直接移步到上一篇评论区查看。

    因为有几个问题点还蛮重要,所以就上一篇文章新增了一些内容,主要如下:

    • ✅ 新增局部状态管理。如同一个页面多个接口,可以分别管理状态切换;
    • ✅ UI层新增Error,Empty,Success的Callback,开发者可以自由选择是否监听,处理业务逻辑更直观、方便;
    • ✅ 结合第三方库loadSir,统一切换UI。
    • ✅ 请求调用更加简单

    好了,正文开始。

    二、局部请求状态管理

    很多时候app开发,存在同一个界面不同接口的情况,两个接口同时请求,一个成功一个失败,这个时候成功接口继续显示自己的页面,失败接口则显示Error提示界面,如下图

    image.png

    上一篇的封装是将errorLiveData和loadingLiveData全局封装在BaseFragment中,而他们的创建也是在BaseViewModel中,这样就导致多个接口同时请求时,如果某个接口发送错误,就无法区分错误来自哪里。

    如果需要每个接口单独管理自己的状态,那么就需要在ViewModel中创建多个erroeLiveData,这样问题是可以解决,但是会导致代码非常冗余。既然需要每个接口管理不同状态,那就可以新建一个既包含请求返回结果又包含不同状态值的LiveData,将之命名为StateLiveData

    /**
     * MutableLiveData,用于将请求状态分发给UI
     */
    class StateLiveData<T> : MutableLiveData<BaseResp<T>>() {
    }
    

    而BaseResp中除了请求返回值的公共json外,还需要添加上不同的状态值,我们将状态值分为( STATE_CREATE,STATE_LOADING,STATE_SUCCESS,STATE_COMPLETED,STATE_EMPTY,STATE_FAILED, STATE_ERROR,STATE_UNKNOWN)几种

    enum class DataState {
        STATE_CREATE,//创建
        STATE_LOADING,//加载中
        STATE_SUCCESS,//成功
        STATE_COMPLETED,//完成
        STATE_EMPTY,//数据为null
        STATE_FAILED,//接口请求成功但是服务器返回error
        STATE_ERROR,//请求失败
        STATE_UNKNOWN//未知
    }
    

    将DataState添加到BaseResp中,

    /**
     * json返回的基本类型
     */
    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该如何使用呢?

    我们都知道数据请求会有不同的结果,成功,异常或者数据为null,那么就可以利用不同的结果,将相应的状态设置在BaseResp的DataState中。直接进入到数据请求Repository层,对上篇异常处理做了改进。

    open class BaseRepository {
        /**
         * repo 请求数据的公共方法,
         * 在不同状态下先设置 baseResp.dataState的值,最后将dataState 的状态通知给UI
         */
        suspend fun <T : Any> executeResp(
            block: suspend () -> BaseResp<T>,
            stateLiveData: StateLiveData<T>
        ) {
            var baseResp = BaseResp<T>()
            try {
                baseResp.dataState = DataState.STATE_LOADING
                //开始请求数据
                val invoke = block.invoke()
                //将结果复制给baseResp
                baseResp = invoke
                if (baseResp.errorCode == 0) {
                    //请求成功,判断数据是否为空,
                    //因为数据有多种类型,需要自己设置类型进行判断
                    if (baseResp.data == null || baseResp.data is List<*> && (baseResp.data as List<*>).size == 0) {
                        //TODO: 数据为空,结构变化时需要修改判空条件
                        baseResp.dataState = DataState.STATE_EMPTY
                    } else {
                        //请求成功并且数据为空的情况下,为STATE_SUCCESS
                        baseResp.dataState = DataState.STATE_SUCCESS
                    }
    
                } else {
                    //服务器请求错误
                    baseResp.dataState = DataState.STATE_FAILED
                }
            } catch (e: Exception) {
                //非后台返回错误,捕获到的异常
                baseResp.dataState = DataState.STATE_ERROR
                baseResp.error = e
            } finally {
                stateLiveData.postValue(baseResp)
            }
        }
    }
    

    executeResp()为数据请求的公共方法,该方法传入了两个参数,第一个是将数据请求函数当作参数,第二个就是上面新建的StateLiveData

    方法一开始就新建了一个BaseResp<T>()对象,将DataState.STATE_LOADING状态设置给BaseResp的dataState,接着开始对数据请求进行异常处理(具体可查看上一篇),如果code=0表示接口请求成功,否则表示接口请求成功,服务器返回错误。在code=0时,对返回数据进行判空处理,因为数据有多种类型,这里需要自己设置类型进行判断,为空就将状态设置为DataState.STATE_EMPTY,否则为

    DataState.STATE_SUCCESS。如果抛出异常,则将状态设置为DataState.STATE_ERROR,在请求结束后,利用stateLiveData将带有状态的baseResp分发给UI。

    到这里,请求状态都设置完成,接下来只需要根据不同状态,开始进行界面切换处理。

    三、结合LoadSir界面切换

    LoadSir是一个加载反馈页管理框架,状态页自动切换,具体使用在这里就不描述了,需要的可移步github查看。

    LiveData接收数据变化时,UI会先注册一个接收事件的观察者,接收到请求的数据后就进行UI更新,第二节里将不同状态也添加到了数据中,要想对状态也进行监听的话,就需要对Observer进行状态处理。

    /**
     * LiveData Observer的一个类,
     * 主要结合LoadSir,根据BaseResp里面的State分别加载不同的UI,如Loading,Error
     * 同时重写onChanged回调,分为onDataChange,onDataEmpty,onError,
     * 开发者可以在UI层,每个接口请求时,直接创建IStateObserver,重写相应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) {
    
                            //数据刚开始请求,loading
                            DataState.STATE_CREATE, DataState.STATE_LOADING -> resultCode =
                                LoadingCallback::class.java
                            //请求成功
                            DataState.STATE_SUCCESS -> resultCode = SuccessCallback::class.java
                            //数据为空
                            DataState.STATE_EMPTY -> resultCode =
                                EmptyCallback::class.java
                            DataState.STATE_FAILED ,DataState.STATE_ERROR -> {
                                val error: Throwable? = t.error
                                onError(error)
                                //可以根据不同的错误类型,设置错误界面时的UI
                                if (error is HttpException) {
                                    //网络错误
                                } else if (error is ConnectException) {
                                    //无网络连接
                                } else if (error is InterruptedIOException) {
                                    //连接超时
                                } else if (error is JsonParseException
                                    || error is JSONException
                                    || error is ParseException
                                ) {
                                    //解析错误
                                } else {
                                    //未知错误
                                }
                                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 -> {
                    //请求成功,数据不为null
                    onDataChange(t.data)
                }
    
                DataState.STATE_EMPTY -> {
                    //数据为空
                    onDataEmpty()
                }
    
                DataState.STATE_FAILED,DataState.STATE_ERROR->{
                    //请求错误
                    t.error?.let { onError(it) }
                }
                else -> { }
            }
    
            //加载不同状态界面
            Log.d(TAG, "onChanged: mLoadService $mLoadService")
    
            mLoadService?.showWithConvertor(t)
    
        }
    
        /**
         * 请求数据且数据不为空
         */
        open fun onDataChange(data: T?) {
    
        }
    
        /**
         * 请求成功,但数据为空
         */
        open fun onDataEmpty() {
    
        }
    
        /**
         * 请求错误
         */
        open fun onError(e: Throwable?) {
    
        }
    }
    

    IStateObserverObserver接口的实现类,参数传入了一个View,而这个View就是你所要替换的界面,这也就是同个界面,不同模块显示异常不同的关键所在。因为是结合Loadsir,首先需要初始化LoadService,再者通过dataState的状态值,设置不同的Callback,例如Loading时,设置为LoadingCallback,Error时,设置为ErrorCallback,Empty时设置为EmptyCallback,设置完成后,在onChanged回调中统一调用showWithConvertor,也就是切换界面的操作。

    而在onChange回调中,同样根据状态值,分别分发onDataChangeonDataEmptyonError的通知。

    到这里,完成了不同状态界面切换和状态通知的分发工作。

    四、如何使用

    上述基本上将整个流程封装完成,使用起来也相对简便。

    Repository层:

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

    直接就一行代码,executeResp方法中传入api的请求,以及StateLiveData。

    ViewModel层:

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

    调用依旧是一行代码,新建了一个StateLiveData,接着直接在viewModelScope作用域中调用Repository层的网络请求,这里记得将StateLiveData作为参数传进去。

    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注册观察者,与以往不同的是,mViewModel?.mProjectTreeLiveData?.observe()第二个参数替换为了IStateObserver,并且传入了一个View,而这个View代表着的是当请求异常时,你所想替换的UI界面,同时,也多了几个回调,

    • onDataChange:请求成功,数据不为空;
    • onReload:点击重新请求;
    • onDataEmpty:数据为空时;
    • onError:请求失败

    开发者可以通过自己的业务需求,自由的选择监听。

    我们来看看效果。

    80cc44c18c36363d3d8d589814397a48.gif

    五、最后

    这次的整合弥补了一些细节问题,更符合App开发逻辑,当然每个App的业务不同,这就要开发者去定制化一些请求细节,但是协程+Retrofit网络请求的大致思路就是如此。
    更多详细的代码可移步至github

    源码: 组件化+Jetpack+kotlin+mvvm

    请结合【Jetpack篇】协程+Retrofit网络请求状态封装实战

    相关文章

      网友评论

        本文标题:【Jetpack篇】协程+Retrofit网络请求状态封装实战(

        本文链接:https://www.haomeiwen.com/subject/ynwxjltx.html