Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试之二

·  阅读 513

1、前言

在前面的博客Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试中,我们已经实现了一个简易的网络请求框架,它基本可以满足我们的网络请求,并且有多种回调状态。但是多写几个接口之后就会开始烦了:为啥每次请求都要在onStart() 中启动加载动画,然后又要在onFinish() 中关闭呢?还有,请求失败可不可以不要手动在onFailure中处理,而是自动弹出Toast 呢?总而言之就一句话:能不能少写或者不写这些重复的代码呢?下面我们就来探讨探讨。

一般来说,一个项目中的加载动画的样式都是固定的(我在这里用了一个LoadingDialog ,就是一个简单的显示ProgressBarDialogFragment );错误的提示也是一样(比如这里的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层(ActivityFragment),刷新UI

在过去,我们最常用的是EventBus ,现在用上MVVM 了,是否有更好的方法呢?

3、全局ViewModel

MVVM模式中,ViewModel用于管理和存储界面相关数据,也就是LiveData。仔细想想,我们在请求网络数据时,用的就是LiveData (loginLiveData),数据发生变化通知View层刷新界面。只不过我们的MainViewModel生命周期跟随着MainActivityMainActivity 完成后,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的生命周期是由传递给ViewModelProviderLifecycler 决定的,所以需要让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()
        }
    })
}
复制代码

由于有个别接口可能不需要加载动画和错误提示,这里加了具名参数isShowLoadingisShowErrorToast,默认为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()
            }
        }
    }
}
复制代码

然后让项目中所有的ActivityFragment都继承BaseActivity或者BaseFragment即可。

5、代码下载

Github(注意要选择dev2.0分支)

6、参考资料

分类:
Android
标签:
分类:
Android
标签: