写一个MVVM快速开发框架(一)基础类封装

写一个MVVM快速开发框架(一)基础类封装用MVVM+Jetpack组件的优点就不用我说了,写过的人肯定都说爽,此次就是想要重新整理下一些基础开发工具,封装一个自己用的顺手的MVVM模式快速开发框架。

前言

最近想要将老项目用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,导致模板代码非常多。

先列出参考博客:

  1. juejin.cn/post/696091…
  2. github.com/hi-dhl/Bind…
  3. github.com/kirich1409/…

之前使用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中:

  1. 修改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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注