从0快速搭建一个实用的MVVM框架(超详细)
目录
- 前言
- 基于MVVM进行快速开发,上手即用。(重构已完成,正在编写SampleApp)
- 如何集成
- 1.继承BaseApplication
- 2.创建ViewModel扩展函数
- 3.引入一键生成代码插件(可选)
- 框架结构
- mvvm
- Activity封装
- Fragment封装
- Adapter封装
- LiveData封装
- Navigation封装
- ViewModel封装
- 辅助类封装
- 管理类封装
- mvvm_navigation
- mvvm_network
项目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation组件。
支持动态加载多状态布局:加载中、成功、失败、标题;
支持快速生成ListActivity、ListFragment;
支持使用插件快速生成适用于本框架的Activity、Fragment、ListActivity、ListFragment。
完整文章前往Github浏览
前言
随着Jetpack
的完善,对于开发者来说,MVVM
显得越来越高效与方便。
对于使用MVVM
的公司来说,都有一套自己的MVVM
框架,但是我发现有些只是对框架进行非常简单的封装,导致在开发过程中会出现很多没必要的冗余代码。
这篇文章主要就是分享如何从0搭建一个高效的MVVM
框架。
基于MVVM进行快速开发, 上手即用。(重构已完成,正在编写SampleApp)
对基础框架进行模块分离, 分为已开发一键生成代码模板, 创建适用于本框架的Activity和Fragment. 具体查看AlvinMVVMPlugin_4_3MVVM Library
--MVVM Navigation Library
--MVVM Network Library
可基于业务需求使用MVVM Library
、MVVM Navigation Library
、MVVM Network Library
【从0快速搭建一个实用的MVVM框架(超详细)】
如何集成 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 只负责网络处理 implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag' // MVVM Navigation 组件抽离 implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag'}
说明 | 依赖地址 | 版本号 |
---|---|---|
MVVM 基类 | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_framework:Tag' | |
MVVM Network | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag' | |
MVVM Navigation | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag' |
1.继承BaseApplication
创建你的Application类,继承
BaseApplication
,并且需要在onCreate
函数中进行配置和初始化相关参数,可以在这里配置网络请求框架的参数和UI全局参数。比如拦截器和多域名,全局Activity和Fragment属性。// 全局Activity设置GlobalMVVMBuilder.initSetting(BaseActivitySetting(), BaseFragmentSetting())private fun initHttpManager() {// 参数拦截器HttpManager.instance.setting {// 设置网络属性setTimeUnit(TimeUnit.SECONDS) // 时间类型 秒, 框架默认值 毫秒setReadTimeout(30) // 读取超时 30s, 框架默认值 10000LsetWriteTimeout(30) // 写入超时 30s, 框架默认值 10000LsetConnectTimeout(30) // 链接超时 30s,框架默认值 10000LsetRetryOnConnectionFailure(true) // 超时自动重连, 框架默认值 truesetBaseUrl("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 请求参数",responseTag = "HTTP Response 返回参数")// 添加拦截器setInterceptorList(hashSetOf(ResponseInterceptor(), ParameterInterceptor()))}}// 需要重写,传入当前是否初始Debug模式。override fun isLogDebug(): Boolean {// 是否显示日志return BuildConfig.DEBUG
2.创建ViewModel扩展函数
所有模块需要依赖的base模块创建ViewModel相关的扩展函数VMKxt和Json实体类壳BaseEntity。
/** * 过滤服务器结果,失败抛异常 * @param block 请求体方法,必须要用suspend关键字修饰 * @param success 成功回调 * @param error 失败回调 可不传 * @param isLoading 是否显示 Loading 布局 * @param loadingMessage 加载框提示内容 */funBaseViewModel.request(block: suspend () -> BaseResponse ,success: (T?) -> Unit,error: (ResponseThrowable) -> Unit = {},isLoading: Boolean = false,loadingMessage: String? = null): Job {// 开始执行请求httpCallback.beforeNetwork.postValue(// 执行Loading逻辑LoadingEntity(isLoading,loadingMessage?.isNotEmpty() == true,loadingMessage ?: ""))return viewModelScope.launch {kotlin.runCatching {//请求体block()}.onSuccess {// 网络请求成功, 结束请求httpCallback.afterNetwork.postValue(false)//校验请求结果码是否正确,不正确会抛出异常走下面的onFailurekotlin.runCatching {executeResponse(it) { coroutine ->success(coroutine)}}.onFailure { error ->// 请求时发生异常, 执行失败回调val responseThrowable = ExceptionHandle.handleException(error)httpCallback.onFailed.value = https://www.it610.com/article/responseThrowable.errorMsg ?:""responseThrowable.errorLog?.let { errorLog ->LogUtil.e(errorLog)}// 执行失败的回调方法error(responseThrowable)}}.onFailure { error ->// 请求时发生异常, 执行失败回调val responseThrowable = ExceptionHandle.handleException(error)httpCallback.onFailed.value = https://www.it610.com/article/responseThrowable.errorMsg ?:""responseThrowable.errorLog?.let { errorLog ->LogUtil.e(errorLog)}// 执行失败的回调方法error(responseThrowable)}}}/** * 不过滤服务器结果 * @param block 请求体方法,必须要用suspend关键字修饰 * @param success 成功回调 * @param error 失败回调 可不传 * @param isLoading 是否显示 Loading 布局 * @param loadingMessage 加载框提示内容 */fun BaseViewModel.requestNoCheck(block: suspend () -> T,success: (T) -> Unit,error: (ResponseThrowable) -> Unit = {},isLoading: Boolean = false,loadingMessage: String? = null): Job {// 开始执行请求httpCallback.beforeNetwork.postValue(// 执行Loading逻辑LoadingEntity(isLoading,loadingMessage?.isNotEmpty() == true,loadingMessage ?: ""))return viewModelScope.launch {runCatching {//请求体block()}.onSuccess {// 网络请求成功, 结束请求httpCallback.afterNetwork.postValue(false)//成功回调success(it)}.onFailure { error ->// 请求时发生异常, 执行失败回调val responseThrowable = ExceptionHandle.handleException(error)httpCallback.onFailed.value = https://www.it610.com/article/responseThrowable.errorMsg ?:""responseThrowable.errorLog?.let { errorLog ->LogUtil.e(errorLog)}// 执行失败的回调方法error(responseThrowable)}}}/** * 请求结果过滤,判断请求服务器请求结果是否成功,不成功则会抛出异常 */suspend fun executeResponse(response: BaseResponse ,success: suspend CoroutineScope.(T?) -> Unit) {coroutineScope {when {response.isSuccess() -> {success(response.getResponseData())}else -> {throw ResponseThrowable(response.getResponseCode(),response.getResponseMessage(),response.getResponseMessage())}}}}
以上代码封装了快速的网络请求扩展函数,并且可以根据自己的情况,选择脱壳或者不脱壳的回调处理。 调用示例:
/** * 加载列表数据 */fun getArticleListData(page: Int, pageSize: Int) {request({filterArticleList(page, pageSize)}, {// 成功操作it?.let {_articleListData.postValue(it.datas)}})}
完成上面的操作,你就可以进入愉快的开发工作了。
3.引入一键生成代码插件(可选)
每次创建Activity、Fragment、ListActivity、ListFragment都是重复的工作,为了可以更高效的开发,减少这些枯燥的操作,特地编写的快速生成MVVM代码的插件,该插件只适用于当前MVVM框架,具体使用请前往AlvinMVVMPlugin。集成后你就可以开始像创建
EmptyActivity
这样创建MVVMActivity
。框架结构
mvvm
该组件对Activity和Fragment进行常用属性封装
base
包下封装了MVVM
的基础组件。activity
实现DataBinding + ViewModel
的封装,以及一些其他功能。adapter
实现DataBinding + Adapter
的封装。fragment
实现DataBinding + ViewModel
的封装,以及一些其他功能。livedata
实现LiveData
的基础功能封装,如基本数据类型的非空返回值。view_model
实现BaseViewModel
的处理。
help
包下封装了组件的辅助类,在BaseApplication中进行全局Actiivty、Fragment属性赋值。manager
包下封装了对Activity的管理。utils
包下封装了LogUtil工具类,通过BaseApplication进行初始化。
Activity封装
AbstractActivity
是Activity
的抽象基类,这个类里面的方法适用于全部Activity
的需求。 该类中封装了所有Activity必须实现的抽象方法。BaseActivity
封装了基础的Activity
功能,主要用来初始化Activity
公共功能:DataBinding
的初始化、沉浸式状态栏、AbstractActivity
抽象方法的调用、屏幕适配、空白区域隐藏软键盘。具体功能可以自行新增。BaseDialogActivity
只负责显示Dialog Loading
弹窗,一般在提交请求或本地流处理时使用。也可以扩展其他的Dialog
,比如时间选择器之类。BaseContentViewActivity
是对布局进行初始化操作的Activity
,他是我们的核心。这里处理了每个Activity
的每个状态的布局,一般情况下有:TitleLayout
公共标题ContentLayout
主要的内容布局,使我们需要程序内容的主要容器。ErrorLayout
当网络请求发生错误,需要对用户进行友好的提示。LoadingLayout
正在加载数据的布局,给用户一个良好的体验,避免首次进入页面显示的布局没有数据。
BaseVMActivity
实现ViewMode
的Activity
基类,通过泛型对ViewModel
进行实例化。并且通过BaseViewModel
进行公共操作。BaseMVVMActivity
所有Activity
最终需要继承的MVVM
类,通过传入DataBinding
和ViewModel
的泛型进行初始化操作,在构造参数中还需要获取Layout
布局BaseListActivity
适用于列表的Activity
,分页操作、上拉加载、下拉刷新、空布局、头布局、底布局封装。
Fragment封装
根据你的需要进行不同的封装,我比较倾向于和
Activity
具有相同功能的封装,也就是Activity
封装的功能我Fragment
也要有。这样在使用Navigation
的时候可以减少Activity
和Fragment
的差异。这里直接参考Activity的封装Adapter封装
每个项目中肯定会有列表的页面,所以还需要对
Adapter
进行DataBinding
适配,这里使用的Adapter是BRVAH。abstract class BaseBindingListAdapter(@LayoutRes private val layoutResId: Int) : BaseQuickAdapter (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
在使用的时候会出现数据倒灌的情况,用简单的话来描述数据倒灌:A订阅1月1日新闻信息,B订阅1月15日新闻信息,但是B在1月15日同时收到了1月1日的信息,这明显不符合我们生活中的逻辑,所以需要对LiveData
进行封装,详细的可以查看KunMinX
的**UnPeek-LiveData**。Navigation封装
通过重写
FragmentNavigator
将原来的 FragmentTransaction.replace()
方法替换为 hide()/Show()
ViewModel封装
在
BaseViewModel
中封装一个网络请求需要用的LiveData
,下面是一个简单的示例open class BaseViewModel : ViewModel() {// 默认的网络请求LiveDataval httpCallback: HttpCallback by lazy { HttpCallback() }inner class HttpCallback {/*** 请求发生错误** String = 网络请求异常*/val onFailed by lazy { StringLiveData() }/*** 请求开始** LoadingEntity 显示loading的实体类*/val beforeNetwork by lazy { EventLiveData() }/*** 请求结束后框架自动对 loading 进行处理** false 关闭 loading or Dialog* true 不关闭 loading or Dialog*/val afterNetwork by lazy { BooleanLiveData() }}}
辅助类封装
大部分的
Activity
和Fragment
样式基本相同,比如布局中的TitleLayout
、LoadingLayout
这些都是统一样式。所以可以封装全局的辅助类来对Activity中的属性进行抽离。- 定义接口
ISettingBaseActivity
添加抽离的方法,并且赋于默认值。 - 定义接口
ISettingBaseFragment
添加抽离的方法,并且赋于默认值。 - 创建
ISettingBaseActivity
和ISettingBaseFragment
的实现类,进行默认的自定义操作。 - 创建
GlobalMVVMBuilder
进行赋值
管理类封装
通过
Lifecycle
结合AppManager
对Activity的进出栈管理。mvvm_navigation
分离Navigation,通过重写 FragmentNavigator 将原来的 FragmentTransaction.replace() 方法替换为 hide()/Show()。
mvvm_network
使用
Retrofit
+ OkHttp
+ Moshi
对网络请求进行封装,使用密封类自定义异常处理。到此这篇关于从0搭建一个实用的MVVM框架的文章就介绍到这了,更多相关MVVM框架搭建内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- 如何用three.js(webgl)搭建3D粮仓、3D仓库、3D物联网设备监控-第十二课
- mysql|mysql 主从同步不一致_MYSQL主从复制数据不一致解决方案
- 关于Spring|MySQL 主从复制数据不一致,怎么办()
- POSTMAN从入门到精通系列(二十六)(使用标签)
- spring|SpringBoot 2整合SpringSecurity权限管理(一)SpringSecurity介绍及简单搭建
- java|SpringBoot从入门到入门学习笔记
- java|EasyExcel 在java中实现从数据库导入导出
- 软件测试|2021年软件测试从入门到进阶资源合集免费分享(案例+视频+笔记+源码)
- Arcgis|【从0开始学GIS】ArcGIS中的绘图基础操作(一)
- Arcgis|【从0开始学GIS】ArcGIS中的绘图基础操作(三)