從0快速搭建一個實用的MVVM框架(超詳細)
結合Jetpack,構建快速開發(fā)的MVVM框架。
項目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation組件。
支持動態(tài)加載多狀態(tài)布局:加載中、成功、失敗、標題;
支持快速生成ListActivity、ListFragment;
支持使用插件快速生成適用于本框架的Activity、Fragment、ListActivity、ListFragment。
完整文章前往Github瀏覽
前言
隨著
Jetpack的完善,對于開發(fā)者來說,MVVM顯得越來越高效與方便。對于使用
MVVM的公司來說,都有一套自己的MVVM框架,但是我發(fā)現(xiàn)有些只是對框架進行非常簡單的封裝,導致在開發(fā)過程中會出現(xiàn)很多沒必要的冗余代碼。這篇文章主要就是分享如何從0搭建一個高效的
MVVM框架。
基于MVVM進行快速開發(fā), 上手即用。(重構已完成,正在編寫SampleApp)
對基礎框架進行模塊分離, 分為
MVVM Library--MVVM Navigation Library--MVVM Network Library可基于業(yè)務需求使用MVVM Library、MVVM Navigation Library、MVVM Network Library
已開發(fā)一鍵生成代碼模板, 創(chuàng)建適用于本框架的Activity和Fragment. 具體查看AlvinMVVMPlugin_4_3
如何集成
To get a Git project into your build:
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}Step 2. Add the dependency
dependencies {
// MVVM 基類
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_framework:Tag'
// MVVM Network 只負責網(wǎng)絡處理
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag'
// MVVM Navigation 組件抽離
implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag'
}依賴引入后,需要初始化依賴,下面是模塊化初始化流程。
1.繼承BaseApplication
創(chuàng)建你的Application類,繼承BaseApplication,并且需要在onCreate函數(shù)中進行配置和初始化相關參數(shù),可以在這里配置網(wǎng)絡請求框架的參數(shù)和UI全局參數(shù)。比如攔截器和多域名,全局Activity和Fragment屬性。
// 全局Activity設置
GlobalMVVMBuilder.initSetting(BaseActivitySetting(), BaseFragmentSetting())
private fun initHttpManager() {
// 參數(shù)攔截器
HttpManager.instance.setting {
// 設置網(wǎng)絡屬性
setTimeUnit(TimeUnit.SECONDS) // 時間類型 秒, 框架默認值 毫秒
setReadTimeout(30) // 讀取超時 30s, 框架默認值 10000L
setWriteTimeout(30) // 寫入超時 30s, 框架默認值 10000L
setConnectTimeout(30) // 鏈接超時 30s,框架默認值 10000L
setRetryOnConnectionFailure(true) // 超時自動重連, 框架默認值 true
setBaseUrl("https://www.wanandroid.com") // 默認域名
// 多域名配置
setDomain {
Constant.domainList.forEach { map ->
map.forEach {
if (it.key.isNotEmpty() && it.value.isNotEmpty()) {
put(it.key, it.value)
}
}
}
}
setLoggingInterceptor(
isDebug = BuildConfig.DEBUG,
hideVerticalLine = true,
requestTag = "HTTP Request 請求參數(shù)",
responseTag = "HTTP Response 返回參數(shù)"
)
// 添加攔截器
setInterceptorList(hashSetOf(ResponseInterceptor(), ParameterInterceptor()))
}
}
// 需要重寫,傳入當前是否初始Debug模式。
override fun isLogDebug(): Boolean {
// 是否顯示日志
return BuildConfig.DEBUG2.創(chuàng)建ViewModel擴展函數(shù)
所有模塊需要依賴的base模塊創(chuàng)建ViewModel相關的擴展函數(shù)VMKxt和Json實體類殼BaseEntity。
/**
* 過濾服務器結果,失敗拋異常
* @param block 請求體方法,必須要用suspend關鍵字修飾
* @param success 成功回調
* @param error 失敗回調 可不傳
* @param isLoading 是否顯示 Loading 布局
* @param loadingMessage 加載框提示內容
*/
fun <T> BaseViewModel.request(
block: suspend () -> BaseResponse<T>,
success: (T?) -> Unit,
error: (ResponseThrowable) -> Unit = {},
isLoading: Boolean = false,
loadingMessage: String? = null
): Job {
// 開始執(zhí)行請求
httpCallback.beforeNetwork.postValue(
// 執(zhí)行Loading邏輯
LoadingEntity(
isLoading,
loadingMessage?.isNotEmpty() == true,
loadingMessage ?: ""
)
)
return viewModelScope.launch {
kotlin.runCatching {
//請求體
block()
}.onSuccess {
// 網(wǎng)絡請求成功, 結束請求
httpCallback.afterNetwork.postValue(false)
//校驗請求結果碼是否正確,不正確會拋出異常走下面的onFailure
kotlin.runCatching {
executeResponse(it) { coroutine ->
success(coroutine)
}
}.onFailure { error ->
// 請求時發(fā)生異常, 執(zhí)行失敗回調
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調方法
error(responseThrowable)
}
}.onFailure { error ->
// 請求時發(fā)生異常, 執(zhí)行失敗回調
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調方法
error(responseThrowable)
}
}
}
/**
* 不過濾服務器結果
* @param block 請求體方法,必須要用suspend關鍵字修飾
* @param success 成功回調
* @param error 失敗回調 可不傳
* @param isLoading 是否顯示 Loading 布局
* @param loadingMessage 加載框提示內容
*/
fun <T> BaseViewModel.requestNoCheck(
block: suspend () -> T,
success: (T) -> Unit,
error: (ResponseThrowable) -> Unit = {},
isLoading: Boolean = false,
loadingMessage: String? = null
): Job {
// 開始執(zhí)行請求
httpCallback.beforeNetwork.postValue(
// 執(zhí)行Loading邏輯
LoadingEntity(
isLoading,
loadingMessage?.isNotEmpty() == true,
loadingMessage ?: ""
)
)
return viewModelScope.launch {
runCatching {
//請求體
block()
}.onSuccess {
// 網(wǎng)絡請求成功, 結束請求
httpCallback.afterNetwork.postValue(false)
//成功回調
success(it)
}.onFailure { error ->
// 請求時發(fā)生異常, 執(zhí)行失敗回調
val responseThrowable = ExceptionHandle.handleException(error)
httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
responseThrowable.errorLog?.let { errorLog ->
LogUtil.e(errorLog)
}
// 執(zhí)行失敗的回調方法
error(responseThrowable)
}
}
}
/**
* 請求結果過濾,判斷請求服務器請求結果是否成功,不成功則會拋出異常
*/
suspend fun <T> executeResponse(
response: BaseResponse<T>,
success: suspend CoroutineScope.(T?) -> Unit
) {
coroutineScope {
when {
response.isSuccess() -> {
success(response.getResponseData())
}
else -> {
throw ResponseThrowable(
response.getResponseCode(),
response.getResponseMessage(),
response.getResponseMessage()
)
}
}
}
}以上代碼封裝了快速的網(wǎng)絡請求擴展函數(shù),并且可以根據(jù)自己的情況,選擇脫殼或者不脫殼的回調處理。 調用示例:
/**
* 加載列表數(shù)據(jù)
*/
fun getArticleListData(page: Int, pageSize: Int) {
request(
{
filterArticleList(page, pageSize)
}, {
// 成功操作
it?.let {
_articleListData.postValue(it.datas)
}
}
)
}完成上面的操作,你就可以進入愉快的開發(fā)工作了。
3.引入一鍵生成代碼插件(可選)
每次創(chuàng)建Activity、Fragment、ListActivity、ListFragment都是重復的工作,為了可以更高效的開發(fā),減少這些枯燥的操作,特地編寫的快速生成MVVM代碼的插件,該插件只適用于當前MVVM框架,具體使用請前往AlvinMVVMPlugin。集成后你就可以開始像創(chuàng)建EmptyActivity這樣創(chuàng)建MVVMActivity。
框架結構
mvvm
該組件對Activity和Fragment進行常用屬性封裝
base包下封裝了MVVM的基礎組件。activity實現(xiàn)DataBinding + ViewModel的封裝,以及一些其他功能。adapter實現(xiàn)DataBinding + Adapter的封裝。fragment實現(xiàn)DataBinding + ViewModel的封裝,以及一些其他功能。livedata實現(xiàn)LiveData的基礎功能封裝,如基本數(shù)據(jù)類型的非空返回值。view_model實現(xiàn)BaseViewModel的處理。
help包下封裝了組件的輔助類,在BaseApplication中進行全局Actiivty、Fragment屬性賦值。manager包下封裝了對Activity的管理。utils包下封裝了LogUtil工具類,通過BaseApplication進行初始化。
Activity封裝
AbstractActivity是Activity的抽象基類,這個類里面的方法適用于全部Activity的需求。 該類中封裝了所有Activity必須實現(xiàn)的抽象方法。BaseActivity封裝了基礎的Activity功能,主要用來初始化Activity公共功能:DataBinding的初始化、沉浸式狀態(tài)欄、AbstractActivity抽象方法的調用、屏幕適配、空白區(qū)域隱藏軟鍵盤。具體功能可以自行新增。BaseDialogActivity只負責顯示Dialog Loading彈窗,一般在提交請求或本地流處理時使用。也可以擴展其他的Dialog,比如時間選擇器之類。BaseContentViewActivity是對布局進行初始化操作的Activity,他是我們的核心。這里處理了每個Activity的每個狀態(tài)的布局,一般情況下有:TitleLayout公共標題ContentLayout主要的內容布局,使我們需要程序內容的主要容器。ErrorLayout當網(wǎng)絡請求發(fā)生錯誤,需要對用戶進行友好的提示。LoadingLayout正在加載數(shù)據(jù)的布局,給用戶一個良好的體驗,避免首次進入頁面顯示的布局沒有數(shù)據(jù)。
BaseVMActivity實現(xiàn)ViewMode的Activity基類,通過泛型對ViewModel進行實例化。并且通過BaseViewModel進行公共操作。BaseMVVMActivity所有Activity最終需要繼承的MVVM類,通過傳入DataBinding和ViewModel的泛型進行初始化操作,在構造參數(shù)中還需要獲取Layout布局BaseListActivity適用于列表的Activity,分頁操作、上拉加載、下拉刷新、空布局、頭布局、底布局封裝。
Fragment封裝
根據(jù)你的需要進行不同的封裝,我比較傾向于和Activity具有相同功能的封裝,也就是Activity封裝的功能我Fragment也要有。這樣在使用Navigation的時候可以減少Activity和Fragment的差異。這里直接參考Activity的封裝
Adapter封裝
每個項目中肯定會有列表的頁面,所以還需要對Adapter進行DataBinding適配,這里使用的Adapter是BRVAH。
abstract class BaseBindingListAdapter<T, DB : ViewDataBinding>(
@LayoutRes private val layoutResId: Int
) : BaseQuickAdapter<T, BaseViewHolder>(layoutResId) {
abstract fun convert(holder: BaseViewHolder, item: T, dataBinding: DB?)
override fun convert(holder: BaseViewHolder, item: T) {
convert(holder, item, DataBindingUtil.bind(holder.itemView))
}
}LiveData封裝
LiveData在使用的時候會出現(xiàn)數(shù)據(jù)倒灌的情況,用簡單的話來描述數(shù)據(jù)倒灌:A訂閱1月1日新聞信息,B訂閱1月15日新聞信息,但是B在1月15日同時收到了1月1日的信息,這明顯不符合我們生活中的邏輯,所以需要對LiveData進行封裝,詳細的可以查看KunMinX的**UnPeek-LiveData**。
Navigation封裝
通過重寫 FragmentNavigator 將原來的 FragmentTransaction.replace() 方法替換為 hide()/Show()
ViewModel封裝
在BaseViewModel中封裝一個網(wǎng)絡請求需要用的LiveData,下面是一個簡單的示例
open class BaseViewModel : ViewModel() {
// 默認的網(wǎng)絡請求LiveData
val httpCallback: HttpCallback by lazy { HttpCallback() }
inner class HttpCallback {
/**
* 請求發(fā)生錯誤
*
* String = 網(wǎng)絡請求異常
*/
val onFailed by lazy { StringLiveData() }
/**
* 請求開始
*
* LoadingEntity 顯示loading的實體類
*/
val beforeNetwork by lazy { EventLiveData<LoadingEntity>() }
/**
* 請求結束后框架自動對 loading 進行處理
*
* false 關閉 loading or Dialog
* true 不關閉 loading or Dialog
*/
val afterNetwork by lazy { BooleanLiveData() }
}
}輔助類封裝
大部分的Activity和Fragment樣式基本相同,比如布局中的TitleLayout、LoadingLayout這些都是統(tǒng)一樣式。所以可以封裝全局的輔助類來對Activity中的屬性進行抽離。
- 定義接口
ISettingBaseActivity添加抽離的方法,并且賦于默認值。 - 定義接口
ISettingBaseFragment添加抽離的方法,并且賦于默認值。 - 創(chuàng)建
ISettingBaseActivity和ISettingBaseFragment的實現(xiàn)類,進行默認的自定義操作。 - 創(chuàng)建
GlobalMVVMBuilder進行賦值
管理類封裝
通過Lifecycle結合AppManager對Activity的進出棧管理。
mvvm_navigation
分離Navigation,通過重寫 FragmentNavigator 將原來的 FragmentTransaction.replace() 方法替換為 hide()/Show()。
mvvm_network
使用 Retrofit + OkHttp + Moshi 對網(wǎng)絡請求進行封裝,使用密封類自定義異常處理。
到此這篇關于從0搭建一個實用的MVVM框架的文章就介紹到這了,更多相關MVVM框架搭建內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android RecyclerView實現(xiàn)吸頂動態(tài)效果流程分析
RecyclerView是Android一個更強大的控件,其不僅可以實現(xiàn)和ListView同樣的效果,還有優(yōu)化了ListView中的各種不足。其可以實現(xiàn)數(shù)據(jù)縱向滾動,也可以實現(xiàn)橫向滾動(ListView做不到橫向滾動)。接下來講解RecyclerView的用法2022-12-12
Android?RecyclerView實現(xiàn)九宮格效果
這篇文章主要為大家詳細介紹了Android?RecyclerView實現(xiàn)九宮格效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06

