在今年的二月份,我在發佈本 Repo:基於 ViewModel + LiveData + Retrofit + RxJava 封裝的網絡請求框架 的同時,也寫了一篇博客介紹了項目特點以及實現思路:ViewModel_Retrofit_RxJava_1.0,詳細地介紹了一步步封裝的過程,也陸續得到了一些讀者的正向反饋,讓我意識到了一些不足點,而到如今六月份,公司的項目也逐漸地轉爲了 Kotlin,所以也促使我來寫第二篇博客git
本 Repo 現現在對應着兩個版本github
1.0 版本即 master 分支,是使用 Java 語言寫的,也是最初始的版本,其實現思路能夠看這裏:ViewModel_Retrofit_RxJava_1.0,用了比較大的篇幅介紹了我封裝此網絡請求框架的思路,建議讀者首先去看下該文章json
2.0 版本即 kotlin 分支,顧名思義,我用 Kotlin 重寫了一遍,固然也不只僅只是簡單的轉換了下語言而已,也解決了 1.0 版本不太理想或者不太合理的地方,也得益於 Kotlin 語言的簡潔性,使得整個基礎庫更加得短小精悍,表達能力也顯得更增強大服務器
看到個人第一篇博客的同窗應該知道,在請求網絡的過程當中界面層的一些通用操做(startLoading、dismissLoading、showToast)是須要由 BaseActivity 與 BaseFragment 經過監聽 LiveData 的數據變化來完成的,1.0 版本受限於 JDK 版本,在 BaseActivity 與 BaseFragment 須要分別寫較多的重複代碼而沒法提取到接口中,而經過 Kotlin 來完成的話就會相對簡潔不少網絡
/** * 做者:leavesC * 時間:2019/5/31 9:38 * 描述: */
interface IBaseViewModelEvent {
fun showLoading(msg: String)
fun showLoading() {
showLoading("")
}
fun dismissLoading()
fun showToast(msg: String)
fun finishView()
fun pop()
}
interface IIBaseViewModelEventObserver : IBaseViewModelEvent {
fun initViewModel(): BaseViewModel? {
return null
}
fun initViewModelList(): MutableList<BaseViewModel>? {
return null
}
fun getLifecycleOwner(): LifecycleOwner
fun initViewModelEvent() {
var list: MutableList<BaseViewModel>? = null
val initViewModelList = initViewModelList()
if (initViewModelList.isNullOrEmpty()) {
val baseViewModel = initViewModel()
baseViewModel?.let {
list = mutableListOf(baseViewModel)
}
} else {
list = initViewModelList
}
list?.let {
observeEvent(list!!)
}
}
fun observeEvent(viewModelList: MutableList<BaseViewModel>) {
for (viewModel in viewModelList) {
viewModel.baseActionEvent.observe(getLifecycleOwner(), Observer { it ->
it?.let {
when (it.action) {
BaseViewModelEvent.SHOW_LOADING_DIALOG -> {
showLoading(it.message)
}
BaseViewModelEvent.DISMISS_LOADING_DIALOG -> {
dismissLoading()
}
BaseViewModelEvent.SHOW_TOAST -> {
showToast(it.message)
}
BaseViewModelEvent.FINISH -> {
finishView()
}
BaseViewModelEvent.POP -> {
pop()
}
}
}
})
}
}
fun getLContext(): Context
fun <T> startActivity(clazz: Class<T>) {
getLContext().startActivity(Intent(getLContext(), clazz))
}
override fun showToast(msg: String) {
Toast.makeText(getLContext(), msg, Toast.LENGTH_SHORT).show()
}
}
複製代碼
BaseActivity 中只要實現幾個特定方法便可,BaseFragment 也同樣如此app
abstract class BaseActivity : AppCompatActivity(), IIBaseViewModelEventObserver {
private var loadDialog: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initViewModelEvent()
}
override fun getLContext(): Context = this
override fun getLifecycleOwner(): LifecycleOwner = this
override fun showLoading(msg: String) {
if (loadDialog == null) {
loadDialog = ProgressDialog(getLContext())
loadDialog!!.setCancelable(false)
loadDialog!!.setCanceledOnTouchOutside(false)
}
loadDialog?.let {
if (!it.isShowing) {
it.show()
}
}
}
override fun dismissLoading() {
loadDialog?.let {
if (it.isShowing) {
it.dismiss()
}
}
}
override fun finishView() {
finish()
}
override fun pop() {
}
fun <T : BaseViewModel> getViewModel(clazz: Class<T>) =
ViewModelProviders.of(this).get(clazz)
}
複製代碼
在 1.0 版本中,RetrofitManagement 與 BaseRemoteDataSource 之間牽扯較多,BaseRemoteDataSource 調用了幾個本應不應由 RetrofitManagement 來實現的方法,使得邏輯較爲複雜凌亂框架
在 2.0 版本中,RetrofitManagement 變爲專職只負責提供 ApiService.class
對象, 具體的網絡請求調用以及數據解析都只由 BaseRemoteDataSource 來中轉控制ide
@SuppressLint("CheckResult")
private fun <T> execute(observable: Observable<BaseResponse<T>>, observer: Observer<OptionT<T>>, quietly: Boolean) {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
if (!quietly) {
showLoading()
}
}.doFinally {
dismissLoading()
}.flatMap(object : Function<BaseResponse<T>, ObservableSource<OptionT<T>>> {
override fun apply(t: BaseResponse<T>): ObservableSource<OptionT<T>> {
when {
t.code == HttpConfig.CODE_SUCCESS || t.message == "OK" -> {
val optional: OptionT<T> = OptionT(t.data)
return createData(optional)
}
else -> {
throw ServerResultException(t.message ?: "未知錯誤", t.code)
}
}
}
}).subscribeWith(observer)
}
private fun <T> createData(t: T): Observable<T> {
return Observable.create { emitter ->
try {
emitter.onNext(t)
emitter.onComplete()
} catch (e: Exception) {
emitter.onError(e)
}
}
}
複製代碼
有時候,在請求一個單純修改狀態值的接口時,服務器返回的數據多是這樣的,此時接口只是經過 code 來反饋該接口已經請求成功,而不須要返回特定數據,所以 data 直接爲 null優化
{
"code": 200,
"msg": "ok",
"data": null
}
複製代碼
這就會衍生出一個問題,因爲 RxJava 2.0 版本後不容許出現 emitter.onNext(null)
傳遞 null 值的狀況,因此致使數據傳遞鏈路就會中斷,雖說此時只要要求後臺同事返回一個空字符串便可解決問題,可有時候溝通並不老是那麼理想……ui
所以就須要 app 端本身來解決了,2.0 版本是經過多加一個包裝類 OptionT
來解決該問題的
class OptionT<T>(val value: T)
複製代碼
在解析數據時,將咱們實際須要的數據 data 包裝在 OptionT 當中,此時就無需關心 data 是否爲 null ,
override fun apply(t: BaseResponse<T>): ObservableSource<OptionT<T>> {
when {
t.code == HttpConfig.CODE_SUCCESS || t.message == "OK" -> {
val optional: OptionT<T> = OptionT(t.data)
return createData(optional)
}
else -> {
throw ServerResultException(t.message ?: "未知錯誤", t.code)
}
}
}
private fun <T> createData(t: T): Observable<T> {
return Observable.create { emitter ->
try {
emitter.onNext(t)
emitter.onComplete()
} catch (e: Exception) {
emitter.onError(e)
}
}
}
複製代碼
在 BaseSubscriber 中再將 data 取出來並傳遞給 RequestCallback ,以此規避 null 值不可傳遞的問題
class BaseSubscriber<T> constructor(private val requestCallback: RequestCallback<T>) :
DisposableObserver<OptionT<T>>() {
override fun onNext(t: OptionT<T>) {
requestCallback.onSuccess(t.value)
}
override fun onError(e: Throwable) {
e.printStackTrace()
val msg = e.message ?: "未知錯誤"
if (requestCallback is RequestMultiplyCallback) {
if (e is BaseException) {
requestCallback.onFail(e)
} else {
requestCallback.onFail(ServerResultException(msg))
}
} else {
ToastHolder.showToast(msg = msg)
}
}
override fun onComplete() {
}
}
複製代碼
其實經過 OptionT
這個名字,不少讀者應該就能夠聯想到 JDK 1.8
新增的 Option
類了吧?其實此處就是借鑑該了其思想
我以爲有必要介紹的優化點就以上三個了,其它地方就留待讀者本身去發現了
爲了方便讀者理解,這裏我也提供了一個 Demo 供讀者參考,2.0 版本其自己就是一個完整的天氣預報App,讀者能夠自行下載運行