1、前言
在前面的博客Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试中,我们已经实现了一个简易的网络请求框架,它基本可以满足我们的网络请求,并且有多种回调状态。但是多写几个接口之后就会开始烦了:为啥每次请求都要在onStart() 中启动加载动画,然后又要在onFinish() 中关闭呢?还有,请求失败可不可以不要手动在onFailure中处理,而是自动弹出Toast 呢?总而言之就一句话:能不能少写或者不写这些重复的代码呢?下面我们就来探讨探讨。
一般来说,一个项目中的加载动画的样式都是固定的(我在这里用了一个LoadingDialog ,就是一个简单的显示ProgressBar 的DialogFragment );错误的提示也是一样(比如这里的Toast)。因此,我们完全可以将其封装起来,在适当的时机调用就可以了。
那么,在何处显示加载动画和错误提示呢?
2、在何处显示加载动画和错误提示?
既然是要封装,那么自然要往底层中走了。很容易就能想到,这个地方就是扩展函数observeState()中的onStart()回调:
inline fun <T> LiveData<BaseResponse<T>>.observeState(
owner: LifecycleOwner,
crossinline callback: HttpRequestCallback<T>.() -> Unit
) {
val requestCallback = HttpRequestCallback<T>().apply(callback)
observe(owner, object : IStateObserver<T> {
override fun onStart() {
requestCallback.startCallback?.invoke()
}
override fun onSuccess(data: T) {
requestCallback.successCallback?.invoke(data)
}
override fun onEmpty() {
requestCallback.emptyCallback?.invoke()
}
override fun onFailure(e: RequestException) {
requestCallback.failureCallback?.invoke(e)
}
override fun onFinish() {
requestCallback.finishCallback?.invoke()
}
})
}
但是这里的代码不涉及到View层接下来,所以接下来,我们需要想办法让将消息发送到View层(Activity或Fragment),刷新UI 。
在过去,我们最常用的是EventBus ,现在用上MVVM 了,是否有更好的方法呢?
3、全局ViewModel
在MVVM模式中,ViewModel用于管理和存储界面相关数据,也就是LiveData。仔细想想,我们在请求网络数据时,用的就是LiveData (loginLiveData),数据发生变化通知View层刷新界面。只不过我们的MainViewModel生命周期跟随着MainActivity,MainActivity 完成后,MainViewModel也随之销毁。那么假如有一个ViewModel,它的生命周期跟随着整个App,我们是不是可以利用它的LiveData实现数据的变化和监听呢?
答案是肯定的。前提是这个ViewModel的生命周期必须由Application 来传递。我们先创建一个EventViewModel :
class EventViewModel : ViewModel() {
val loadingLiveData = MutableLiveData<Boolean>()
val toastLiveData = MutableLiveData<String?>()
fun showLoading() {
loadingLiveData.value = true
}
fun dismissLoading() {
loadingLiveData.value = false
}
fun showToast(msg: String?) {
toastLiveData.value = msg
}
}
里面有两个LiveData,分别负责加载动画和错误提示这两块信息。然后创建Application,在里面实现EventViewModel:
class App : Application(), ViewModelStoreOwner {
private val appViewModelProvider by lazy {
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
)
}
override fun getViewModelStore(): ViewModelStore = ViewModelStore()
companion object{
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
lateinit var eventViewModel: EventViewModel
}
override fun onCreate() {
super.onCreate()
context = applicationContext
eventViewModel = appViewModelProvider[EventViewModel::class.java]
}
}
ViewModel的生命周期是由传递给ViewModelProvider的Lifecycler 决定的,所以需要让Application继承ViewModelStoreOwner,让其具备保持ViewModel的能力。
有了全局的ViewModel,我们就可以在observeState()函数中改变其中的LiveData的值,用于发送需要显示加载动画和错误提示的消息了:
inline fun <T> LiveData<BaseResponse<T>>.observeState(
owner: LifecycleOwner,
isShowLoading: Boolean = true,
isShowErrorToast: Boolean = true,
crossinline callback: HttpRequestCallback<T>.() -> Unit
) {
val requestCallback = HttpRequestCallback<T>().apply(callback)
observe(owner, object : IStateObserver<T> {
override fun onStart() {
if (isShowLoading) {
App.eventViewModel.showLoading()
}
requestCallback.startCallback?.invoke()
}
override fun onSuccess(data: T) {
requestCallback.successCallback?.invoke(data)
}
override fun onEmpty() {
requestCallback.emptyCallback?.invoke()
}
override fun onFailure(e: RequestException) {
if (isShowErrorToast) {
App.eventViewModel.showToast(e.errorMsg)
}
requestCallback.failureCallback?.invoke(e)
}
override fun onFinish() {
if (isShowLoading) {
App.eventViewModel.dismissLoading()
}
requestCallback.finishCallback?.invoke()
}
})
}
由于有个别接口可能不需要加载动画和错误提示,这里加了具名参数isShowLoading 和isShowErrorToast,默认为true,也就是默认是需要,如果不需要,传入false即可。
4、View层刷新界面
终于到了最后一步,只需要在View层接受信息完成界面刷新就好了。这个工作交给BaseActivity或者BaseFragment完成:
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initLoadingObserver()
initToastObserver()
}
private fun initLoadingObserver() {
App.eventViewModel.loadingLiveData.observe(this) {
if (it == true) {
LoadingDialog.show(this)
} else {
LoadingDialog.dismiss(this)
}
}
}
private fun initToastObserver() {
App.eventViewModel.toastLiveData.observe(this) { msg ->
msg?.takeIf { it.isNotEmpty() }?.also {
Toast.makeText(App.context, it, Toast.LENGTH_SHORT).show()
}
}
}
}
然后让项目中所有的Activity和Fragment都继承BaseActivity或者BaseFragment即可。
5、代码下载
Github(注意要选择dev2.0分支)