项目地址:hegaojian/JetpackMvvm
简介: :chicken::basketball:一个 Jetpack 结合 MVVM 的快速开发框架,基于 MVVM 模式集成谷歌官方推荐的 JetPack 组件库:LiveData、ViewModel、Lifecycle、Navigation 组件 使用 Kotlin 语言,添加大量拓展函数,简化代码 加入 Retrofit 网络请求,协程,帮你简化各种操作,让你快速开发项目
更多:作者 提 Bug 官网
标签:
已用该库重构了我之前的玩安卓项目,利用 Navigation 组件以单 Activity+Fragment 架构编写,优化了很多代码,对比之前的 mvp 项目,开发效率与舒适度要提高了不少,想看之前 MVP 的项目可以去 GitHub - hegaojian/WanAndroid: 【停止维护,已使用Jetpack+Mvvm重构】根据鸿神提供的WanAndroid开放Api来制作的产品级玩安卓App,采用Kotlin语言,基于Material Design+AndroidX +MVP+RxJava+Retrofit等框架开发,注释超详细,方便大家练手
效果图展示
APK 下载:
Github 下载
firm 下载(推荐)
扫码下载(推荐)
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
...
implementation 'com.github.hegaojian:JetpackMvvm:1.2.7'
}
AndroidStudio 4.0 以下版本------>
android {
...
dataBinding {
enabled = true
}
viewBinding {
enabled = true
}
}
AndroidStudio 4.0 及以上版本 ------>
android {
...
buildFeatures {
dataBinding = true
viewBinding = true
}
}
一般我们项目中都会有一套自己定义的符合业务需求的基类 BaseActivity/BaseFragment,所以我们的基类需要继承本框架的 Base 类
Activity:
abstract class BaseActivity : BaseVmDbActivity() {
/**
* 当前 Activity 绑定的视图布局 Id abstract 修饰供子类实现
*/
abstract override fun layoutId(): Int
/**
* 当前 Activityc 创建后调用的方法 abstract 修饰供子类实现
*/
abstract override fun initView(savedInstanceState: Bundle?)
/**
* 创建 liveData 数据观察
*/
override override fun createObserver()
/**
* 打开等待框 在这里实现你的等待框展示
*/
override fun showLoading(message: String) {
...
}
/**
* 关闭等待框 在这里实现你的等待框关闭
*/
override fun dismissLoading() {
...
}
}
Fragment:
abstract class BaseFragment : BaseVmDbFragment() {
abstract override fun initView(savedInstanceState: Bundle?)
/**
* 懒加载 只有当前 fragment 视图显示时才会触发该方法 abstract 修饰供子类实现
*/
abstract override fun lazyLoadData()
/**
* 创建 liveData 数据观察 懒加载之后才会触发
*/
override override fun createObserver()
/**
* Fragment 执行 onViewCreated 后触发的方法
*/
override fun initData() {
}
/**
* 打开等待框 在这里实现你的等待框展示
*/
override fun showLoading(message: String) {
...
}
/**
* 关闭等待框 在这里实现你的等待框关闭
*/
override fun dismissLoading() {
...
}
}
class LoginViewModel : BaseViewModel() {
}
3.2 创建 LoginFragment 继承基类传入相关泛型,第一个泛型为你创建的 LoginViewModel,第二个泛型为 ViewDataBind,保存 fragment_login.xml 后 databinding 会生成一个 FragmentLoginBinding 类。(如果没有生成,试着点击 Build->Clean Project)
class LoginFragment : BaseFragment() {
/**
* 初始化操作
*/
override fun initView(savedInstanceState: Bundle?) {
...
}
/**
* fragment 懒加载
*/
override fun lazyLoadData() {
...
}
}
4.1 新建请求配置类继承 BaseNetworkApi 示例:
class NetworkApi : BaseNetworkApi() {
companion object {
val instance: NetworkApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { NetworkApi() }
//双重校验锁式-单例 封装 NetApiService 方便直接快速调用
val service: ApiService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
instance.getApi(ApiService::class.java, ApiService.SERVER_URL)
}
}
/**
* 实现重写父类的 setHttpClientBuilder 方法,
* 在这里可以添加拦截器,可以对 OkHttpClient.Builder 做任意你想要做的骚操作
*/
override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
builder.apply {
//示例:添加公共 heads,可以存放 token,公共参数等, 注意要设置在日志拦截器之前,不然 Log 中会不显示 head 信息
addInterceptor(MyHeadInterceptor())
// 日志拦截器
addInterceptor(LogInterceptor())
//超时时间 连接、读、写
connectTimeout(10, TimeUnit.SECONDS)
readTimeout(5, TimeUnit.SECONDS)
writeTimeout(5, TimeUnit.SECONDS)
}
return builder
}
/**
* 实现重写父类的 setRetrofitBuilder 方法,
* 在这里可以对 Retrofit.Builder 做任意骚操作,比如添加 GSON 解析器,protobuf 等
*/
override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
return builder.apply {
addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
addCallAdapterFactory(CoroutineCallAdapterFactory())
}
}
}
{
"data": ...,
"errorCode": 0,
"errorMsg": ""
}
该示例格式是 玩 Android Api返回的数据格式,如果 errorCode 等于 0 请求成功,否则请求失败 作为开发者的角度来说,我们主要是想得到脱壳数据-data,且不想每次都判断 errorCode==0 请求是否成功或失败 这时我们可以在服务器返回数据基类中继承 BaseResponse,实现相关方法:data class ApiResponse(var errorCode: Int, var errorMsg: String, var data: T) : BaseResponse() {
// 这里是示例,wanandroid 网站返回的 错误码为 0 就代表请求成功,请你根据自己的业务需求来编写
override fun isSucces() = errorCode == 0
override fun getResponseCode() = errorCode
override fun getResponseData() = data
override fun getResponseMsg() = errorMsg
}
1、将请求数据包装给 ResultState,在 Activity/Fragment 中去监听 ResultState 拿到数据做处理
class RequestLoginViewModel: BaseViewModel {
//自动脱壳过滤处理请求结果,自动判断结果是否成功
var loginResult = MutableLiveData>()
//不用框架帮脱壳
var loginResult2 = MutableLiveData>>()
fun login(username: String, password: String){
//1.在 Activity/Fragment 的监听回调中拿到已脱壳的数据(项目有基类的可以用)
request(
{ HttpRequestCoroutine.login(username, password) }, //请求体
loginResult,//请求的结果接收者,请求成功与否都会改变该值,在 Activity 或 fragment 中监听回调结果,具体可看 loginActivity 中的回调
true,//是否显示等待框,,默认 false 不显示 可以默认不传
"正在登录中..."//等待框内容,可以默认不填请求网络中...
)
//2.在 Activity/Fragment 中的监听拿到未脱壳的数据,你可以自己根据 code 做业务需求操作(项目没有基类的可以用)
requestNoCheck(
{HttpRequestCoroutine.login(username,password)},
loginResult2,
true,
"正在登录中...")
}
class LoginFragment : BaseFragment() {
private val requestLoginRegisterViewModel: RequestLoginRegisterViewModel by viewModels()
/**
* 初始化操作
*/
override fun initView(savedInstanceState: Bundle?) {
...
}
/**
* fragment 懒加载
*/
override fun lazyLoadData() {
...
}
override fun createObserver(){
//脱壳
requestLoginRegisterViewModel.loginResult.observe(viewLifecycleOwner,
Observer { resultState ->
parseState(resultState, {
//登录成功 打印用户
it.username.logd()
}, {
//登录失败(网络连接问题,服务器的结果码不正确...异常都会走在这里)
showMessage(it.errorMsg)
})
})
//不脱壳
requestLoginRegisterViewModel.loginResult2.observe(viewLifecycleOwner, Observer {resultState ->
parseState(resultState,{
if(it.errorCode==0){
//登录成功 打印用户名
it.data.username.logd()
}else{
//登录失败
showMessage(it.errorMsg)
}
},{
//请求发生了异常
showMessage(it.errorMsg)
})
})
}
}
2、 直接在当前 ViewModel 中拿到请求结果
class RequestLoginViewModel : BaseViewModel() {
fun login(username: String, password: String){
//1.拿到已脱壳的数据(项目有基类的可以用)
request({HttpRequestCoroutine.login(username,password)},{
//请求成功 已自动处理了 请求结果是否正常
it.username.logd()
},{
//请求失败 网络异常,或者请求结果码错误都会回调在这里
it.errorMsg.logd()
},true,"正在登录中...")
//2.拿到未脱壳的数据,你可以自己根据 code 做业务需求操作(项目没有基类或者不想框架帮忙脱壳的可以用)
requestNoCheck({HttpRequestCoroutine.login(username,password)},{
//请求成功 自己拿到数据做业务需求操作
if(it.errorCode==0){
//结果正确
it.data.username.logd()
}else{
//结果错误
it.errorMsg.logd()
}
},{
//请求失败 网络异常回调在这里
it.errorMsg.logd()
},true,"正在登录中...")
}
val mainViewModel:MainViewModel by viewModels() 或者 val mainViewModel:MainViewModel by activityViewModels() 获取的 如果请求时要弹出 loading,你需要在 Activity | Fragment 中添加以下代码:
设置全局 jetpackMvvmLog 变量 是否打开请求日志,默认 false 不打印,如需要打印日志功能,请设值为 true
val mainViewModel = ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)
**现在官方 Ktx 有拓展函数可以轻松调用 ``` kotlin //在 activity 中获取当前 Activity 级别作用域的 ViewModel private val mainViewModel:MainViewModel by viewModels()//在 activity 中获取 Application 级别作用域的 ViewModel(注,这个是本框架提供的,Application 类继承框架的 BaseApp 才有用) private val mainViewModel by lazy { getAppViewModel()}
//在 fragment 中获取当前 Fragment 级别作用域的 ViewModel private val mainViewModel:MainViewModel by viewModels()
//在 fragment 中获取父类 Activity 级别作用域的 ViewModel private val mainViewModel:MainViewModel by activityViewModels()
//在 fragment 中获取 Application 级别作用域的 ViewModel(注,这个是本框架提供的,Application 类继承框架的 BaseApp 才有用) private val mainViewModel by lazy { getAppViewModel()}
## 6.写了一些常用的拓展函数
``` kotlin
算了不写了,这个不重要,想具体看的话可以在
me.hgj.jetpackmvvm.ext.util
me.hgj.jetpackmvvm.ext.view
的包中看,反正你也可以自己写,按照自己的喜好与需求来
-keep class me.hgj.jetpackmvvm.**{*;}
################ ViewBinding & DataBinding ###############
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
public static * inflate(android.view.LayoutInflater);
public static * inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
public static * bind(android.view.View);
}
Copyright 2019, hegaojian(何高建)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.