從0快速搭建一個實(shí)用的MVVM框架(超詳細(xì))
結(jié)合Jetpack,構(gòu)建快速開發(fā)的MVVM框架。
項(xiàng)目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation組件。
支持動態(tài)加載多狀態(tài)布局:加載中、成功、失敗、標(biāo)題;
支持快速生成ListActivity、ListFragment;
支持使用插件快速生成適用于本框架的Activity、Fragment、ListActivity、ListFragment。
完整文章前往Github瀏覽
前言
隨著
Jetpack
的完善,對于開發(fā)者來說,MVVM
顯得越來越高效與方便。對于使用
MVVM
的公司來說,都有一套自己的MVVM
框架,但是我發(fā)現(xiàn)有些只是對框架進(jìn)行非常簡單的封裝,導(dǎo)致在開發(fā)過程中會出現(xiàn)很多沒必要的冗余代碼。這篇文章主要就是分享如何從0搭建一個高效的
MVVM
框架。
基于MVVM進(jìn)行快速開發(fā), 上手即用。(重構(gòu)已完成,正在編寫SampleApp)
對基礎(chǔ)框架進(jìn)行模塊分離, 分為
MVVM Library
--MVVM Navigation Library
--MVVM Network Library
可基于業(yè)務(wù)需求使用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 只負(fù)責(zé)網(wǎng)絡(luò)處理 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ù)中進(jìn)行配置和初始化相關(guān)參數(shù),可以在這里配置網(wǎng)絡(luò)請求框架的參數(shù)和UI全局參數(shù)。比如攔截器和多域名,全局Activity和Fragment屬性。
// 全局Activity設(shè)置 GlobalMVVMBuilder.initSetting(BaseActivitySetting(), BaseFragmentSetting()) private fun initHttpManager() { // 參數(shù)攔截器 HttpManager.instance.setting { // 設(shè)置網(wǎng)絡(luò)屬性 setTimeUnit(TimeUnit.SECONDS) // 時間類型 秒, 框架默認(rèn)值 毫秒 setReadTimeout(30) // 讀取超時 30s, 框架默認(rèn)值 10000L setWriteTimeout(30) // 寫入超時 30s, 框架默認(rèn)值 10000L setConnectTimeout(30) // 鏈接超時 30s,框架默認(rèn)值 10000L setRetryOnConnectionFailure(true) // 超時自動重連, 框架默認(rèn)值 true setBaseUrl("https://www.wanandroid.com") // 默認(rèn)域名 // 多域名配置 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())) } } // 需要重寫,傳入當(dāng)前是否初始Debug模式。 override fun isLogDebug(): Boolean { // 是否顯示日志 return BuildConfig.DEBUG
2.創(chuàng)建ViewModel擴(kuò)展函數(shù)
所有模塊需要依賴的base模塊創(chuàng)建ViewModel相關(guān)的擴(kuò)展函數(shù)VMKxt和Json實(shí)體類殼BaseEntity。
/** * 過濾服務(wù)器結(jié)果,失敗拋異常 * @param block 請求體方法,必須要用suspend關(guān)鍵字修飾 * @param success 成功回調(diào) * @param error 失敗回調(diào) 可不傳 * @param isLoading 是否顯示 Loading 布局 * @param loadingMessage 加載框提示內(nèi)容 */ 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)絡(luò)請求成功, 結(jié)束請求 httpCallback.afterNetwork.postValue(false) //校驗(yàn)請求結(jié)果碼是否正確,不正確會拋出異常走下面的onFailure kotlin.runCatching { executeResponse(it) { coroutine -> success(coroutine) } }.onFailure { error -> // 請求時發(fā)生異常, 執(zhí)行失敗回調(diào) val responseThrowable = ExceptionHandle.handleException(error) httpCallback.onFailed.value = responseThrowable.errorMsg ?: "" responseThrowable.errorLog?.let { errorLog -> LogUtil.e(errorLog) } // 執(zhí)行失敗的回調(diào)方法 error(responseThrowable) } }.onFailure { error -> // 請求時發(fā)生異常, 執(zhí)行失敗回調(diào) val responseThrowable = ExceptionHandle.handleException(error) httpCallback.onFailed.value = responseThrowable.errorMsg ?: "" responseThrowable.errorLog?.let { errorLog -> LogUtil.e(errorLog) } // 執(zhí)行失敗的回調(diào)方法 error(responseThrowable) } } } /** * 不過濾服務(wù)器結(jié)果 * @param block 請求體方法,必須要用suspend關(guān)鍵字修飾 * @param success 成功回調(diào) * @param error 失敗回調(diào) 可不傳 * @param isLoading 是否顯示 Loading 布局 * @param loadingMessage 加載框提示內(nèi)容 */ 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)絡(luò)請求成功, 結(jié)束請求 httpCallback.afterNetwork.postValue(false) //成功回調(diào) success(it) }.onFailure { error -> // 請求時發(fā)生異常, 執(zhí)行失敗回調(diào) val responseThrowable = ExceptionHandle.handleException(error) httpCallback.onFailed.value = responseThrowable.errorMsg ?: "" responseThrowable.errorLog?.let { errorLog -> LogUtil.e(errorLog) } // 執(zhí)行失敗的回調(diào)方法 error(responseThrowable) } } } /** * 請求結(jié)果過濾,判斷請求服務(wù)器請求結(jié)果是否成功,不成功則會拋出異常 */ 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)絡(luò)請求擴(kuò)展函數(shù),并且可以根據(jù)自己的情況,選擇脫殼或者不脫殼的回調(diào)處理。 調(diào)用示例:
/** * 加載列表數(shù)據(jù) */ fun getArticleListData(page: Int, pageSize: Int) { request( { filterArticleList(page, pageSize) }, { // 成功操作 it?.let { _articleListData.postValue(it.datas) } } ) }
完成上面的操作,你就可以進(jìn)入愉快的開發(fā)工作了。
3.引入一鍵生成代碼插件(可選)
每次創(chuàng)建Activity、Fragment、ListActivity、ListFragment都是重復(fù)的工作,為了可以更高效的開發(fā),減少這些枯燥的操作,特地編寫的快速生成MVVM代碼的插件,該插件只適用于當(dāng)前MVVM框架,具體使用請前往AlvinMVVMPlugin。集成后你就可以開始像創(chuàng)建EmptyActivity
這樣創(chuàng)建MVVMActivity
。
框架結(jié)構(gòu)
mvvm
該組件對Activity和Fragment進(jìn)行常用屬性封裝
base
包下封裝了MVVM
的基礎(chǔ)組件。activity
實(shí)現(xiàn)DataBinding + ViewModel
的封裝,以及一些其他功能。adapter
實(shí)現(xiàn)DataBinding + Adapter
的封裝。fragment
實(shí)現(xiàn)DataBinding + ViewModel
的封裝,以及一些其他功能。livedata
實(shí)現(xiàn)LiveData
的基礎(chǔ)功能封裝,如基本數(shù)據(jù)類型的非空返回值。view_model
實(shí)現(xiàn)BaseViewModel
的處理。
help
包下封裝了組件的輔助類,在BaseApplication中進(jìn)行全局Actiivty、Fragment屬性賦值。manager
包下封裝了對Activity的管理。utils
包下封裝了LogUtil工具類,通過BaseApplication進(jìn)行初始化。
Activity封裝
AbstractActivity
是Activity
的抽象基類,這個類里面的方法適用于全部Activity
的需求。 該類中封裝了所有Activity必須實(shí)現(xiàn)的抽象方法。BaseActivity
封裝了基礎(chǔ)的Activity
功能,主要用來初始化Activity
公共功能:DataBinding
的初始化、沉浸式狀態(tài)欄、AbstractActivity
抽象方法的調(diào)用、屏幕適配、空白區(qū)域隱藏軟鍵盤。具體功能可以自行新增。BaseDialogActivity
只負(fù)責(zé)顯示Dialog Loading
彈窗,一般在提交請求或本地流處理時使用。也可以擴(kuò)展其他的Dialog
,比如時間選擇器之類。BaseContentViewActivity
是對布局進(jìn)行初始化操作的Activity
,他是我們的核心。這里處理了每個Activity
的每個狀態(tài)的布局,一般情況下有:TitleLayout
公共標(biāo)題ContentLayout
主要的內(nèi)容布局,使我們需要程序內(nèi)容的主要容器。ErrorLayout
當(dāng)網(wǎng)絡(luò)請求發(fā)生錯誤,需要對用戶進(jìn)行友好的提示。LoadingLayout
正在加載數(shù)據(jù)的布局,給用戶一個良好的體驗(yàn),避免首次進(jìn)入頁面顯示的布局沒有數(shù)據(jù)。
BaseVMActivity
實(shí)現(xiàn)ViewMode
的Activity
基類,通過泛型對ViewModel
進(jìn)行實(shí)例化。并且通過BaseViewModel
進(jìn)行公共操作。BaseMVVMActivity
所有Activity
最終需要繼承的MVVM
類,通過傳入DataBinding
和ViewModel
的泛型進(jìn)行初始化操作,在構(gòu)造參數(shù)中還需要獲取Layout
布局BaseListActivity
適用于列表的Activity
,分頁操作、上拉加載、下拉刷新、空布局、頭布局、底布局封裝。
Fragment封裝
根據(jù)你的需要進(jìn)行不同的封裝,我比較傾向于和Activity
具有相同功能的封裝,也就是Activity
封裝的功能我Fragment
也要有。這樣在使用Navigation
的時候可以減少Activity
和Fragment
的差異。這里直接參考Activity的封裝
Adapter封裝
每個項(xiàng)目中肯定會有列表的頁面,所以還需要對Adapter
進(jìn)行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
進(jìn)行封裝,詳細(xì)的可以查看KunMinX
的**UnPeek-LiveData**。
Navigation封裝
通過重寫 FragmentNavigator
將原來的 FragmentTransaction.replace()
方法替換為 hide()/Show()
ViewModel封裝
在BaseViewModel
中封裝一個網(wǎng)絡(luò)請求需要用的LiveData
,下面是一個簡單的示例
open class BaseViewModel : ViewModel() { // 默認(rèn)的網(wǎng)絡(luò)請求LiveData val httpCallback: HttpCallback by lazy { HttpCallback() } inner class HttpCallback { /** * 請求發(fā)生錯誤 * * String = 網(wǎng)絡(luò)請求異常 */ val onFailed by lazy { StringLiveData() } /** * 請求開始 * * LoadingEntity 顯示loading的實(shí)體類 */ val beforeNetwork by lazy { EventLiveData<LoadingEntity>() } /** * 請求結(jié)束后框架自動對 loading 進(jìn)行處理 * * false 關(guān)閉 loading or Dialog * true 不關(guān)閉 loading or Dialog */ val afterNetwork by lazy { BooleanLiveData() } } }
輔助類封裝
大部分的Activity
和Fragment
樣式基本相同,比如布局中的TitleLayout
、LoadingLayout
這些都是統(tǒng)一樣式。所以可以封裝全局的輔助類來對Activity中的屬性進(jìn)行抽離。
- 定義接口
ISettingBaseActivity
添加抽離的方法,并且賦于默認(rèn)值。 - 定義接口
ISettingBaseFragment
添加抽離的方法,并且賦于默認(rèn)值。 - 創(chuàng)建
ISettingBaseActivity
和ISettingBaseFragment
的實(shí)現(xiàn)類,進(jìn)行默認(rèn)的自定義操作。 - 創(chuàng)建
GlobalMVVMBuilder
進(jìn)行賦值
管理類封裝
通過Lifecycle
結(jié)合AppManager
對Activity的進(jìn)出棧管理。
mvvm_navigation
分離Navigation,通過重寫 FragmentNavigator 將原來的 FragmentTransaction.replace() 方法替換為 hide()/Show()。
mvvm_network
使用 Retrofit
+ OkHttp
+ Moshi
對網(wǎng)絡(luò)請求進(jìn)行封裝,使用密封類自定義異常處理。
到此這篇關(guān)于從0搭建一個實(shí)用的MVVM框架的文章就介紹到這了,更多相關(guān)MVVM框架搭建內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android RecyclerView實(shí)現(xiàn)吸頂動態(tài)效果流程分析
RecyclerView是Android一個更強(qiáng)大的控件,其不僅可以實(shí)現(xiàn)和ListView同樣的效果,還有優(yōu)化了ListView中的各種不足。其可以實(shí)現(xiàn)數(shù)據(jù)縱向滾動,也可以實(shí)現(xiàn)橫向滾動(ListView做不到橫向滾動)。接下來講解RecyclerView的用法2022-12-12Android編程判斷當(dāng)前應(yīng)用是否在后臺運(yùn)行的方法示例
這篇文章主要介紹了Android編程判斷當(dāng)前應(yīng)用是否在后臺運(yùn)行的方法,涉及Android針對當(dāng)前程序運(yùn)行狀態(tài)相關(guān)屬性操作與判定技巧,需要的朋友可以參考下2018-03-03android中px和dp,px和sp之間的轉(zhuǎn)換方法
在Android開發(fā)中dp和px,sp和px之間的轉(zhuǎn)換時必不可少的。下面腳本之家小編給大家?guī)砹薬ndroid中px和dp,px和sp之間的轉(zhuǎn)換方法,感興趣的朋友一起看看吧2018-06-06Android實(shí)現(xiàn)簡單的加載進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡單的加載進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05android okhttp的基礎(chǔ)使用【入門推薦】
本文主要總結(jié)了Android著名網(wǎng)絡(luò)框架-okhttp的基礎(chǔ)使用。具有一定的參考價值,下面跟著小編一起來看下吧2017-01-01在Linux下通過命令行打包Android應(yīng)用的方法
這篇文章主要介紹了在Linux下通過命令行打包Android應(yīng)用的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07Android?RecyclerView實(shí)現(xiàn)九宮格效果
這篇文章主要為大家詳細(xì)介紹了Android?RecyclerView實(shí)現(xiàn)九宮格效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06