美文网首页Android
Capybara-MVVM 一个Android基础组件架构

Capybara-MVVM 一个Android基础组件架构

作者: 恒泪 | 来源:发表于2020-05-02 23:14 被阅读0次

    一个基于Android MVVM的基础组件架构🐖

    以下文章,我会把本架构称为 Capybara

    放下Github链接先https://github.com/Ubitar/Capybara-MVVM

    Capybara 使用databinding + fragmentation搭建,仅包含ActivityFragment等组件基础功能,
    可能有些人喜欢用 navigation,架构有分层,大家可以自己fork下来删减定制。

    • 前言
    • 结构简述
    • 食用方法
    • 常见问题

    前言

    在阅读文章前,我会默认大家都看过databinding、fragmentation、LiveData的使用方法,及kotlin的使用,kt真香。

    不过我希望先阅读一下下面链接里的大佬文章,加深对MVVM和数据驱动的了解。
    https://www.zhihu.com/question/30976423/answer/106134677
    https://www.jianshu.com/p/1fcda521fcda 禁止在layout中写复杂逻辑

    当然,如果你做过Vue或者微信小程序那更好理解了,即使那时你会嫌弃安卓的MVVM,或者充满黑人问号。

    结构简述

    Capybara 主要通过让组件继承IView 、IViewModel 、IModel这3个接口来实现的。

    结构

    View 层可以为你的Activity、Fragment或者DialogFragment
    Model 层为为你的业务提供网络请求服务或者数据库读存服务
    ViewModel 中间层 则是负责处理你的业务逻辑,从Model中获取数据进行处理,并对View进行更新的

    图中BaseMvvMActivity(我写错成了BseMvvMActivtivty了)、BaseViewModelBaseModel为MVVM的实现抽象类,在不同的生命周期中实现并调用了MVVM接口。
    BaseMvvMFragment也类似有同样的实现,只不过因其生命周期,调用方法的位置有些不同,具体内容需要大家去浏览源码。

    如果你想支持例如Popup 之类的组件,也可以了解架构的大体走向后通过实现上方所提及的3个接口进行实现,当然,前提是你的这类popup组件得有一个说得过去的生命周期。

    食用方法

    1、新建一个布局文件,里面就只有一个按钮
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
        <data>
            <variable
                name="viewModelName"
                type="com.example.example.demo1.Demo1ViewModel" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:animateLayoutChanges="true"
            android:orientation="vertical">
            <Button
                android:id="@+id/btn1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text='@{"点击这个按钮  "+viewModelName.count}'
                android:textAllCaps="false"
                app:onClick="@{viewModelName.onClickBtn1}"
                tools:text="点击这个按钮" />
        </LinearLayout>
    </layout>
    
    2、新建一个ViewModel,继承自BaseActivityViewModel

    注意:这个ViewModel是继承自ActivityViewModel的,因为我们接下来要创建的是其相应的Activity,如果你要创建的是Fragment,那么你需要继承的则是BaseFragmentViewModel,同理,DialogBaseDialogViewModel

    class Demo1ViewModel(application: Application) : BaseActivityViewModel<BaseModel>(application) {
    
        val count = MutableLiveData<Int>(0)
    
        /** 这个是 MVVM 中的  Model层,如没有网络或数据库需求,传NUll即可 */
        override fun getModel(): Class<BaseModel>? = null
    
        /** 创建自己的业务的Actions 并转达给父类,Actions用于与Activity交流,后面会讲 ,此处传NUll即可*/
        override fun onCreateActions(): ActivityActions? =null
    
        //用于初始化RecyclerView adapter的事件监听
        override fun initEvent() {
            super.initEvent()
        }
    
        //初始化数据或者获取数据
        override fun initData() {
            super.initData()
        }
    
        fun onClickBtn1(view: View) {
            count.value = count.value!! + 1
        }
    }
    
    3、新建你的Activity ,继承 BaseActivity并实现getLayoutId()getViewModelId()提供布局Id和ViewModel在布局中的变量名称
    class Demo1Activity: BaseActivity<ActivityDemo1Binding, Demo1ViewModel>() {
    
        /** 你的布局Id **/
        override fun getLayoutId(inflater: LayoutInflater, savedInstanceState: Bundle?): Int = R.layout.activity_demo1
    
        /** 布局中ViewModel的Name  */
        override fun getViewModelId(): Int =BR.viewModelName
    
        /**
         * 下方函数运行顺序(从上往下顺序运行)
         *
         *  initParams()
         *  onCreatedViewModel()  /   initDaggerInject()
         *  onBeforeObservable()
         *  onBindObservable()
         *  initViewModelParams()
         *  initView()
         *  ViewModel.initEvent()
         *  ViewModel.initData()
         *
         */
        //用于接收并处理从上一个界面传递过来的数据
        override fun initParams() {
            super.initParams()
        }
    
        override fun onCreatedViewModel() {
            super.onCreatedViewModel()
        }
    
        //这里可以进行Dagger注入
        override fun initDaggerInject() {
            super.initDaggerInject()
        }
    
        override fun onBeforeObservable() {
            super.onBeforeObservable()
        }
    
        //如果有需要,可以通过这个函数把初始数据传到ViewModel
        override fun initViewModelParams() {
            super.initViewModelParams()
            viewModel.count.value = 100
        }
    
        // 初始化RecyclerView或者其他View
        override fun initView() {
            super.initView()
        }
    
        // 注册ViewModel中变量值的改变,可用于ViewModel向View传递信息或操作
        override fun onBindObservable() {
            super.onBindObservable()
        }
    }
    
    4、或许你的项目里会有读取数据库或者网络的时候,那么你就需要实现相应的Model层,编写一个Model层文件
    class Demo3Model(
        private val viewModel: Demo3ViewModel
    ) : BaseModel(viewModel) {
    
        fun toLogin(account: String, password: String) {
            //模拟登录
            Flowable.timer(1000, TimeUnit.MILLISECONDS)
                .doOnSubscribe { ToastUtils.showShort("加载中") }
                    //这个是AutoDispose  如果要Rxlifecycle你要去想办法自己更换
    //            .`as`(AutoDisposeUtil.fromOnDestroy(viewModel.lifecycle.get()!!))
                .subscribe({
                    viewModel.afterLoginSuccess()
                }, {
                    println(it)
                }).isDisposed
        }
    
        override fun onCleared() {
            super.onCleared()
        }
    }
    

    在ViewModel处编写getModel()函数的返回值,同时别忘了修改类上方的泛型

    class Demo3ViewModel(application: Application) : BaseActivityViewModel<Demo3Model>(application) {
    
        override fun getModel(): Class<Demo3Model>? = Demo3Model::class.java
    
        /** 创建自己的业务的Demo1Actions ,并转达给父类 ,若无需Actions,传NUll即可*/
        override fun onCreateActions(): ActivityActions? =null
    
        var account = MutableLiveData<String>("")
        var password = MutableLiveData<String>("")
    
        fun onClickLogin(view: View) {
            model.toLogin(account.value ?: "", password.value ?: "")
        }
    
        fun afterLoginSuccess() {
            ToastUtils.showShort("登陆成功")
        }
    
    }
    
    5、Actions的使用:ViewModel如何和View(Activity)通信

    既然前面有说过ViewModel不持有View的对象,那ViewModel怎么告诉View执行一些UI上的事呢,来看下下图。

    ViewModel通过Actions给View发送消息 ViewModel通过建立一个专属的Actions行为集合类,由View负责监听,ViewModel负责发送的方式传递行为意图,View接收后做出响应。举个例子:让View显示一个Toast
    (1)创建Actions类:建立该ViewModelActions(行为集合),该Actions继承自ActivityActionsFragmentViewModel对应的是FragmentActionsActions集合中的CustomAction是本次交流的行为类型,是承载消息的主体。
    class Demo2Actions : ActivityActions() {
    
        val customAction: CustomAction by lazy { CustomAction() }
    
        class CustomAction: SingleLiveAction<Int>() {
            override fun describe(): String {
                return "自定义的Action,可以订阅这个Action实现ViewModel向Activity或者Fragment等进行单向传递消息," +
                        "注意,我这里传的是一个Int格式,有需要可以换成你要的其他格式,比如bean类,这ViewModel和View的交流也只有这种办法了"
            }
        }
    }
    

    (2)构建Actions对象:在其ViewModel中创建Actions的对象,通过onCreateActions传给父类(ViewModel父类封装的部分通用功能需要Actions才能使用)

    class Demo2ViewModel(application: Application) : BaseActivityViewModel<BaseModel>(application) {
    
        //@Inject  如果是用了Dagger可以通过注入赋值
        val actions = Demo2Actions()
    
        /** 这个是 MVVM 中的  Model层,如没有网络或数据库需求,传NUll即可 */
        override fun getModel(): Class<BaseModel>? = null
    
        /** 创建自己的业务的Demo1Actions ,并转达给父类 ,若无需Actions,传NUll即可*/
        override fun onCreateActions(): ActivityActions? =actions
    
        fun onClickBtn5(view: View) {
            actions2.customAction.call(100)
        }
    }
    

    (3)在View监听ViewModelActions发送的行为消息:

    class Demo2Activity: BaseActivity<ActivityDemo2Binding, Demo2ViewModel>() {
        override fun getLayoutId(inflater: LayoutInflater, savedInstanceState: Bundle?): Int = R.layout.activity_demo2
    
        override fun getViewModelId(): Int =BR.viewModel
    
        override fun onBindObservable() {
            super.onBindObservable()
            viewModel.actions.customAction.observe(this, Observer {
                ToastUtils.showShort("Activity收到了来自ViewModel的信息 $it")
            })
        }
    }
    

    (4)从ViewModel中调用发送行为消息语句:发送时夹带参数值100

        fun onClickBtn5(view: View) {
            actions.customAction.call(100)
        }
    


    6、关于fragmentation在这个架构上的应用可以查看源码demo4

    常见问题

    1、这架构胶水代码好多

    额。。。。这个我也想解决,但是奈何我还不会写模板一键生成

    2、MVVM好用不

    这个问题就很刁钻了,MVVM和MVP一个自底向上,一个自顶向下,写法完全相反,而且MVVM并不是android原生就有的,稍微复杂点的逻辑不能写在xml(或者说太多if条件),xml文件因databinding编译出错的时候没有具体错误信息,只能一个一个注释掉来排查。你想把项目从MVP移植到MVVM?对不起,大部分组件都得重新改造。
    但是好处还是有的,感触最深的还是改了只改变一个变量就显示隐藏了多个View,其次是@BindingAdapter对UI操作的封装,其他的还要自己去体会。

    大哥,觉得可以的话Gayhub给个Star吧,他们是有交互效果的啊

    知道这是啥不

    相关文章

      网友评论

        本文标题:Capybara-MVVM 一个Android基础组件架构

        本文链接:https://www.haomeiwen.com/subject/xjxcwhtx.html