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

一个基于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
http://08643.cn/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继承自ActivityActions,FragmentViewModel对应的是FragmentActions。Actions集合中的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吧,他们是有交互效果的啊

知道这是啥不
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352