前言
最近想要将老项目用MVVM模式去重构,原来的App采用MVP+MVVM的混合模式,老项目嘛大家都懂,最开始用MVP,后来慢慢改成MVVM,但是又没完全重构,所以整个项目看起来乱糟糟的,每次新加功能的时候写的那叫一个难受。
工欲善其事必先利其器
用MVVM+Jetpack组件的优点就不用我说了,写过的人肯定都说爽,此次就是想要重新整理下一些基础开发工具,封装一个自己用的顺手的MVVM模式快速开发框架。
一是平常用来写测试,二是以便在需要的时候快速投入使用。
明确思路
写之前需要明确好自己的思路,自己需要用的东西,是否熟悉,Jetpcak有很多组件,我们平常开发中不太可能全部用到,有需要的时候再加上也不迟。比如:
- App架构是采用
单activity+多fragment
架构,还是采用传统的多activty模式。
- 比如是否需要
组件化
开发,如果项目不大或者是单人开发,可以不需要组件化。
- 比如选择databinding还是viewbidning
- 比如选择
LiveData
or Flow
or Rx
- …
动手之前先想好你需要什么,有些可能不太成熟的框架可以尝鲜,但是投入使用需要谨慎考虑,不然后期的维护可能体验到什么是一把辛酸泪…
步入正题:基础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,导致模板代码非常多。
先列出参考博客:
- https://juejin.cn/post/6960914424865488932
- https://github.com/hi-dhl/Binding
- https://github.com/kirich1409/ViewBindingPropertyDelegate
之前使用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() if (!isLoaded && !isHidden && lazyInit) { initData() isLoaded = true } }
abstract fun initData()
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) 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 = ""
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 {
private val map = mutableMapOf<Class<*>, Any>()
private val retrofit by lazy { RetrofitFactory.factory() }
fun <T : Any> getApiService(apiClass: Class<T>): T { return getService(apiClass) }
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 = invoke when(baseResp.code){ 0,200 -> { if (baseResp.data == null || baseResp.data is List<*> && (baseResp.data as List<*>).size == 0) { baseResp.state = NetState.STATE_EMPTY } else { 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 { })
|
最后附上[Demo地址