前言
最近想要将老项目用MVVM模式去重构,原来的App采用MVP+MVVM的混合模式,老项目嘛大家都懂,最开始用MVP,后来慢慢改成MVVM,但是又没完全重构,所以整个项目看起来乱糟糟的,每次新加功能的时候写的那叫一个难受。
工欲善其事必先利其器
用MVVM+Jetpack组件的优点就不用我说了,写过的人肯定都说爽,此次就是想要重新整理下一些基础开发工具,封装一个自己用的顺手的MVVM模式快速开发框架。 一是平常用来写测试,二是以便在需要的时候快速投入使用。
明确思路
写之前需要明确好自己的思路,自己需要用的东西,是否熟悉,Jetpcak有很多组件,我们平常开发中不太可能全部用到,有需要的时候再加上也不迟。比如:
- App架构是采用
单activity+多fragment
架构,还是采用传统的多activty模式。 - 比如是否需要
组件化
开发,如果项目不大或者是单人开发,可以不需要组件化。 - 比如选择databinding还是viewbidning
- 比如选择
LiveData
orFlow
orRx
- …
动手之前先想好你需要什么,有些可能不太成熟的框架可以尝鲜,但是投入使用需要谨慎考虑,不然后期的维护可能体验到什么是一把辛酸泪…..
步入正题:基础Activity封装
最基础的BaseActivity():
abstract class BaseActivity(@LayoutRes private val layout: Int ?= null) : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
layout?.let {
setContentView(it)
}
initData(savedInstanceState)
}
abstract fun initData(savedInstanceState: Bundle?=null)
}
我这里选择直接传入layout id进行布局绑定,尽量简洁明了
对DataBindingBaseActivity封装:
abstract class DataBindingBaseActivity<T : ViewDataBinding>(@LayoutRes private val layout: Int) : BaseActivity() {
private var _mBinding: T ?= null
val mBinding get() = _mBinding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_mBinding = DataBindingUtil.setContentView(this, layout)
}
override fun onDestroy() {
super.onDestroy()
_mBinding?.unbind()
}
}
DataBindingBaseActivity 继承 BaseActivity,开放mBinding给子类使用。
使用ViewBinding
并不是所有人都喜欢DataBinding,但是一定所有人都喜欢ViewBinding(我猜的哈哈哈),平常我用到viewBinding比较多,dataBinding只会在少数几个页面用到,之前一直是直接创建binding,导致模板代码非常多。
先列出参考博客:
之前使用findViewById(),后来使用butterKnife,在后来使用kotlin直接使用控件id,说实话我挺讨厌直接使用控件id的,所幸这种方式在之后也会被抛弃。
怎样使用viewbinding在这里就不做介绍了,现在有大佬发现了使用反射或委托去创建viewBinding,请参考链接一,也有现成的库去使用,参考链接二 链接三其覆盖了activity,fragment,dialog,adapter等多种使用场景。
因为viewbinding的创建已经非常简单了,所以我不再将其封装在BaseActivity中,最终activity使用如下:
class MainActivity : BaseActivity(R.layout.activity_main){
private val binding by viewBinding(ActivityMainBinding::bind)
private val viewmodel by viewModels<CommonViewModel>()
override fun initData(savedInstanceState: Bundle?) {
}
}
此处viewmode创建方法见:官网
基础Fragment封装
abstract class BaseFragment(@LayoutRes private val layout: Int, private val lazyInit:Boolean = false) : Fragment(layout) {
val TAG by lazy {
this.javaClass.name;
}
private var isLoaded = false
lateinit var mContext: Context
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (!lazyInit){
initData()
}
}
override fun onResume() {
super.onResume()
//Fragment是否可见
if (!isLoaded && !isHidden && lazyInit) {
initData()
isLoaded = true
}
}
/** * 初始化数据 */
abstract fun initData()
/** * fragment跳转,防止重复点击崩溃 */
fun navigate(destination: Int, bundle: Bundle ?= null) = NavHostFragment.findNavController(this).apply {
currentDestination?.getAction(destination)?.let {
navigate(destination,bundle)
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
}
我个人倾向使用单activity+多fragment
架构,使用Navigation
作为导航,基本能应对大部分场景。navigate()
方法是为了防止重复点击崩溃。lazyInit
参数代表是否延迟加载
DataBindingBaseFragment
abstract class DataBindingBaseFragment<T : ViewDataBinding>(@LayoutRes private val layout: Int,lazyInit:Boolean = false) : BaseFragment(layout = layout,lazyInit = lazyInit) {
private var _mBinding: T? = null
val mBinding get() = _mBinding!!
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
_mBinding = DataBindingUtil.inflate(inflater, layout, container, false)
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//LiveData needs the lifecycle owner
mBinding.lifecycleOwner = requireActivity()
}
override fun onDestroyView() {
super.onDestroyView()
_mBinding?.unbind()
}
}
mBinding.lifecycleOwner = requireActivity() 是databinding配合LiveData使用时需要的
使用:
class DataBindingFragment : DataBindingBaseFragment<FragmentDatabindBinding>(R.layout.fragment_databind) {
private val viewmodel by activityViewModels<DataBindViewModel>()
override fun initData() {
//不要忘了赋值
mBinding.viewmodel = viewmodel
}
}
网络框架封装
我的想法是使用 协程
+ ViewModel
+ Repository
去做网络请求,返回结果使用LiveData
保存在ViewModel
中
- Reporisitory的职责就是数据处理,包括本地数据和网络数据
- ViewModel的职责就是连接UI和数据
- LiveData可以进行数据的观察
我们接口返回数据的基础类:
class ApiResponse<T> : Serializable {
var code : Int = 0
var data : T ?= null
var message : Any ?= null
var state : NetState = NetState.STATE_UNSTART
var error : String = ""
/** * 如果服务端data肯定不为null,直接将data返回。 * 假如data为null证明服务端出错,这种错误已经产生并且不可逆,反射生成空对象 * 客户端只需保证不闪退并给予提示即可 */
fun data(): T? {
when (code) {
//请求成功
0, 200 -> {
if (null==data){
data = Any::class.java.newInstance() as T
}
return data
}
}
throw ApiException(message as String, code)
}
}
一般接口返回的就是code
data
message
,如果有其他的可以自行更改,其中state是状态类,如下:
enum class NetState {
STATE_UNSTART,//未知
STATE_LOADING,//加载中
STATE_SUCCESS,//成功
STATE_EMPTY,//数据为null
STATE_FAILED,//接口请求成功但是服务器返回error
STATE_ERROR//请求失败
}
ApiException是自定义的异常抛出类:
class ApiException(val errorMessage: String, val errorCode: Int) : Throwable()
定义接口:
interface Api {
@GET("banner/json")
fun loadProjectTree(): ApiResponse<List<BannerData>>
}
创建RetrofitFactory:
object RetrofitFactory {
private val okHttpClientBuilder: OkHttpClient.Builder by lazy {
OkHttpClient.Builder()
.readTimeout(
10000,
TimeUnit.MILLISECONDS
)
.connectTimeout(
10000,
TimeUnit.MILLISECONDS
)
}
fun factory(): Retrofit {
val okHttpClient = okHttpClientBuilder.build()
val retrofit = Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://www.wanandroid.com/")
.build()
return retrofit
}
}
创建Retrofit管理类:
object RetrofitManager {
/** * 用于存储ApiService */
private val map = mutableMapOf<Class<*>, Any>()
private val retrofit by lazy {
RetrofitFactory.factory()
}
//动态指定域名
fun <T : Any> getApiService(apiClass: Class<T>): T {
return getService(apiClass)
}
/** * 获取ApiService单例对象 */
private fun <T : Any> getService(apiClass: Class<T>): T{
return if (map[apiClass] == null) {
val t = retrofit.create(apiClass)
if (map[apiClass] == null) {
map[apiClass] = t
}
t
} else {
map[apiClass] as T
}
}
}
在Repo中的调用:
class CommonRepo() {
private var mService: Api = RetrofitManager.getApiService(Api::class.java)
fun laod() = mService.loadProjectTree()
}
我们可以使用将网络请求封装在BaseRepo中:
- 修改API接口中的方法,使用suspend修饰
interface Api {
@GET("banner/json")
suspend fun loadProjectTree(): ApiResponse<List<BannerData>>
}
2.BaseRepo中需传入协程,在viewmodel中创建repo的时候最好使用viewModelScope
open class BaseRepository(private val coroutineScope: CoroutineScope) {
protected fun <T> launch( block : suspend () -> ApiResponse<T>, success: suspend (ApiResponse<T>) -> Unit ){
coroutineScope.launch(Dispatchers.IO){
var baseResp = ApiResponse<T>()
try {
baseResp.state = NetState.STATE_LOADING
//开始请求数据
val invoke = block.invoke()
//将结果复制给baseResp
baseResp = invoke
when(baseResp.code){
0,200 -> {
//请求成功,判断数据是否为空
if (baseResp.data == null || baseResp.data is List<*> && (baseResp.data as List<*>).size == 0) {
//TODO: 数据为空,结构变化时需要修改判空条件
baseResp.state = NetState.STATE_EMPTY
} else {
//请求成功并且数据为空的情况下,为STATE_SUCCESS
baseResp.state = NetState.STATE_SUCCESS
}
}
400,401 -> {
baseResp.state = NetState.STATE_FAILED
}
}
}catch (e:Exception){
baseResp.state = NetState.STATE_ERROR
e.printStackTrace()
}finally {
success(baseResp)
}
}
}
}
- 通过NetState设置请求状态
- block代表一个返回ApiResponse的suspend方法
- success代表返回一个ApiResponse类型的对象
Repo中调用调用:
fun laod(resultLiveData: MutableLiveData<ApiResponse<List<BannerData>>>){
launch(
block = {
mService.loadProjectTree()
},
success = {
resultLiveData.postValue(it)
}
)
}
如果你觉得MutableLiveData<ApiResponse>看起来头疼,可以再进行一次封装:
class ResultLiveData<T> : MutableLiveData<ApiResponse<T>>() {
}
最终网络请求完整流程:
viewModel:
class CommonViewModel : ViewModel() {
private val repo by lazy { CommonRepo(viewModelScope) }
val liveData = ResultLiveData<List<BannerData>>()
fun load(){
repo.laod(liveData)
}
}
repo:
class CommonRepo(scope: CoroutineScope) : BaseRepository(scope) {
private var mService: Api = RetrofitManager.getApiService(Api::class.java)
fun laod(resultLiveData: ResultLiveData<List<BannerData>>){
launch(
block = {
mService.loadProjectTree()
},
success = {
resultLiveData.postValue(it)
}
)
}
}
UI:
commonViewModel.load()
commonViewModel.liveData.observe(viewLifecycleOwner, Observer {
//do something
})
最后附上mvvm_develop项目地址,文章代码略有缺失,完整请查看demo,项目整体还在完善中,欢迎大佬们指点。卑微Androider在线求个Star😅
参考:
写一个MVVM快速开发框架(一)基础类封装: juejin.cn/post/698991…
写一个MVVM快速开发框架(二)组件化改造: juejin.cn/post/699508…
写一个MVVM快速开发框架(三)单Activity+多Fragment模式 juejin.cn/post/699698…
今天的文章写一个MVVM快速开发框架(一)基础类封装分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22094.html