今天我们利用LiveData,ViewModel,retrofit2,kotlin协程来搭建一个MVVM的网络请求框架,利用数据来驱动UI更新变化,将数据和UI进行分离。
1.新建一个ApiService接口,由于我们常用的网络请求是get和post,所以这里利用retrofit定义这两张请求类型的公共方法,由于retrofit2中已经支持了对协程的支持,所以抽取的get和post方法如下:
interface ApiService {
@GET
suspend fun httpGet(@Url url: String,@QueryMap map:Map): ApiResponse
@POST
suspend fun httpPost(@Url url: String,@FieldMap map: Map): ApiResponse
}
2.ApiResponse是我们提取的一个公共实体类,没什么好说的,一看就就知道意思,其中定义来一个枚举,用来表示接口请求状态的,后面回用到,具体代码如下:
data class ApiResponse(
@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 getData(
apiName: String,
type: Type,
host: String = baseUrl,
map: Map = HashMap()
): ApiResponse {
var apiResponse = ApiResponse(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 getData(
apiName: String,
type: Type,
host: String = baseUrl,
map: Map = HashMap()
): ApiResponse {
var apiResponse = ApiResponse(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>>()
然后定义一个请求具体接口的方法:
/**
* 获取首页banner图
*/
private fun getBannerData() {
viewModelScope.launch {
var bannerList = getData>(
apiName = "banner/json",
type = object : TypeToken>>() {}.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 AppLiveData>.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,或者接口,互不影响。