利用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,或者接口,互不影响。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269