Bootstrap

利用kotlin协程和retrofit2,LiveData,ViewModel实现一个简单的网络请求框架

今天我们利用LiveData,ViewModel,retrofit2,kotlin协程来搭建一个MVVM的网络请求框架,利用数据来驱动UI更新变化,将数据和UI进行分离。

1.新建一个ApiService接口,由于我们常用的网络请求是get和post,所以这里利用retrofit定义这两张请求类型的公共方法,由于retrofit2中已经支持了对协程的支持,所以抽取的get和post方法如下:

interface  ApiService {

    @GET
    suspend fun <T> httpGet(@Url url: String,@QueryMap map:Map<String,String>): ApiResponse<T>

    @POST
    suspend fun <T> httpPost(@Url url: String,@FieldMap map: Map<String, String>): ApiResponse<T>
}

2.ApiResponse是我们提取的一个公共实体类,没什么好说的,一看就就知道意思,其中定义来一个枚举,用来表示接口请求状态的,后面回用到,具体代码如下:

data class ApiResponse<T>(
    @SerializedName("errorCode")
    var code: Int = 0,
    @SerializedName("errorMsg")
    var msg: String = "",
    @SerializedName("data")
    var data: T ?,
    var state: AppState=AppState.LOADING
)
//记录接口状态的枚举
enum class AppState {
    LOADING, SUCCESS, ERROR, EMPTY
}

3.然后定义一个BaseViewModel继承自ViewModel,这个类是这个网络框架中比较重要的一部分了,其中有两个方法,分别是用来请求get,和post请求的,通过在外面获取这个ViewModel对象,然后调用对应的请求方法传入接口名称和参数即可,代码如下:

 /**
     * get请求公共方法
     */
    suspend fun <T> getData(
        apiName: String,
        type: Type,
        host: String = baseUrl,
        map: Map<String, String> = HashMap()
    ): ApiResponse<T> {
        var apiResponse = ApiResponse<T>(data = null)
        var isSuccess = CatchException.catch {
        //由于retrofit2中对协程的支持,所以这里直接取到解析后的json对象
            apiResponse = apiService.httpGet(host + apiName, map)
        }
        //通过返回的结果,处理对应的数据状态
        setState(isSuccess, apiResponse)
        return GsonUtils.fromJson(
            GsonUtils.toJson(apiResponse), type
        )
    }
    
     /**
     * get请求公共方法
     */
    suspend fun <T> getData(
        apiName: String,
        type: Type,
        host: String = baseUrl,
        map: Map<String, String> = HashMap()
    ): ApiResponse<T> {
        var apiResponse = ApiResponse<T>(data = null)
        var isSuccess = CatchException.catch {
        //由于retrofit2中对协程的支持,所以这里直接取到解析后的json对象
            apiResponse = apiService.httpGet(host + apiName, map)
        }
        //通过返回的结果,处理对应的数据状态
        setState(isSuccess, apiResponse)
        return GsonUtils.fromJson(
            GsonUtils.toJson(apiResponse), type
        )
    }
    
     /**
     * 对接口返回错误的处理,由于在协程中,没有回调函数,所以这里采用try cath来捕获接口异常信息
     */
    object CatchException {
     suspend fun catch(block: suspend() -> Unit) :Boolean{
        try {
            block()
            return true
        } catch (e: Exception) {
            LogUtils.d("BaseAndroid",e.message)
            ExceptionUtil.catchException(e)
        }
         return false
    }
}

4.到这里一个简单的网络请求框架就完成来,现在我们来看看应该怎么使用,首先定义一个ViewModel类,继承自BaseViewModel,在该类中声明一个LiveData对象

//轮播图liveData
    val bannerLiveData = AppLiveData<ApiResponse<List<BannerData>>>()

然后定义一个请求具体接口的方法:

    /**
     * 获取首页banner图
     */
    private fun getBannerData() {
        viewModelScope.launch {
            var bannerList = getData<List<BannerData>>(
                apiName = "banner/json",
                type = object : TypeToken<ApiResponse<List<BannerData>>>() {}.type
            )
            //接口请求完成后,将结果通过liveData对象更新到activity中,
            bannerLiveData.postValue(bannerList)
        }
    }

在activity中初始化刚刚我们定义的viewModel对象,基本上一个页面对应一个ViewModel,每个接口对象一个Livedata对象。如果有多该页面数据需要共享的话也可以公用一个ViewModel和LiveData对象。

    val mainViewModel: MainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }

通过ViewModel对象获取liveData对象观察数据变化,如果liveData数据有变化的话,就回通知UI更新,

   //监听bannerLiveData数据变化,更新UI
        mActivity.mainViewModel.bannerLiveData.appObserve(mActivity, {
            bannerList = it
            bannerView.banner.setImages(it).start()
            bannerView.tvBannerTittle.visibility = View.VISIBLE
            bannerView.tvBannerTittle.text = it[0].title
        },{
        //处理接口请求失败的逻辑,可以不传
        },{
        //处理接口返回空数据的逻辑,可以不传
        })

这里定义了一个LivewData的扩展函数appObserve,主要是用来将接口状态的枚举数据转换成对应的方法,一个是接口请求成功,一个是接口失败,另外一个是数据为空的情况。

fun <T> AppLiveData<ApiResponse<T>>.appObserve(
    activity: AppCompatActivity,
    onSuccess: (T) -> Unit,//接口返回成功 ,必须实现的回调方法
    onError: () -> Unit={},//接口返回失败,如果需要处理失败逻辑时,可以传人错误方法的逻辑
    onEmpty: () -> Unit={} //接口返回空数据 ,如果需要护理空数据逻辑,可以传人空数据方法的处理逻辑
) {
    this.observe(activity, Observer {
        when (it.state) {
            AppState.ERROR -> {
                onError()
            }
            AppState.EMPTY -> {
                onEmpty()
            }
            AppState.SUCCESS -> {
                var data = it.data
                if (data != null) {
                    onSuccess(data)
                } else {
                    onEmpty()
                }
            }
        }
    })
}

到这里一个完整的接口就处理完成,通过liveData和ViewModel完全实现了数据和UI的分离,不管以后UI怎么变化,或者数据接口怎么变化,我们只需要去对应的修改Ui,或者接口,互不影响。

;