基于DataBinding与LiveData的MVVM实践

前言

在MVP模式下,随着业务逻辑的不断增加,UI也会变得越来的多样化,UI改变多样化势必会造成View层的接口case变多,View的接口类就会变得异常庞大。MVVM的方式完美的解决了MVP模式的这一痛点,它采用双向绑定的机制,实现了UI与数据的绑定操作。仅通过改变数据就可以实现对UI的更新操作。


view与数据.png

DataBinding

MVVM在前端领域可谓是如鱼得水,很多著名的框架都是采用的MVVM模式,最近大火的VUE就是其中之一,而在安卓领域,由于一直一来xml的layout都是一个比较功能弱化的存在,所以实现MVVM双向绑定就变得比较困难,直到谷歌推出了实现视图和数据双向绑定的工具DataBinding,才让双向绑定变得操作简单。

初始化

在项目中开启DataBinding只需要在gradle的android下新增以下代码即可:

    dataBinding {
        enabled = true
    }

在xml的编写上,DataBinding有新的写法,最外层不再是页面的根布局,而是layout,layout中包含有生命的绑定class以及根布局。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!--绑定数据类-->
        <variable name="weatherData"
            type="com.yanghaoyi.klivedata.model.bean.WeatherNetData"/>
        <!--绑定逻辑类-->
        <variable
            name="presenter"
            type="com.yanghaoyi.klivedata.view.MainActivity.WeatherPresenter"/>
    </data>
    <!--页面根布局-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </LinearLayout>
</layout>

编写完xml文件后需要rebuild一下工程,rebuild成功后会自动生成继承ViewDataBinding的绑定Class类,是通过layout文件进行命名的,例如activity_main.xml生成的绑定class为ActivityMainBinding。ActivityMainBinding需要在onCreate函数中通过setContentView进行初始化。

  lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //初始化DataBinding
        binding =DataBindingUtil.setContentView(this,R.layout.activity_main)
        binding.presenter = WeatherPresenter()
        init()
    }

建立双向绑定

建立了绑定机制后在xml文件中即可通过使用@{}实现View与Data的双向绑定,以TextView为例,当weatherData.data.ganmao放生改变的时候,tvCold文本组件会自动更新文本显示。??相当于?:表达式,即当weatherData.data.ganmao数据为null的时候,文本组件取值为 ' 详情 '。

        <TextView
            android:id="@+id/tvCold"
            style="@style/Common_Button"
            android:text="@{weatherData.data.ganmao??`详情`}"/>

除了简单的数据绑定,DataBinding还可以为我们实现事件的绑定,以点击按钮从EditText获取文字查询为例,首先定义事件Presenter绑定,binding.presenter = WeatherPresenter()并在xml中建立variable对应关系,WeatherPresenter内部逻辑非常简单,如下:

    /**  点击事件逻辑中心 **/
    open inner class WeatherPresenter{
        open  fun onClick(view:View,city:String){
            when (view.id) {
                R.id.tvSearch -> {
                    this@MainActivity.viewModel.requestWeather(city)
                }
            }
        }
    }

WeatherPresenter的onClick函数需要两个参数,一个是被点击的view,一个是字符串类型的城市名称,在xml中我们只需要@{(view)->presenter.onClick(view,edInput.text.toString)}即可实现tvSearch控件与WeatherPresenter的绑定。

        <EditText
            android:id="@+id/edInput"
            android:hint="@string/please_input_city"
            style="@style/Common_EditText"/>
        <View
            style="@style/Common_CutLine"/>
        <TextView
            android:id="@+id/tvSearch"
            style="@style/Common_Button"
            android:text="@string/search"
            android:onClick="@{(view)->presenter.onClick(view,edInput.text.toString)}"/>
  

LiveData

LiveData 是一个能够感知 Activity 与 Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。LiveData 持有界面代码 Lifecycle 的引用,只有在界面生命周期处于 Started 或 Resumed状态时才会更新数据。当绑定对象销毁,LiveData会自行清理。

结合ViewModel

ViewModel是业务逻辑的控制中心,当数据Data放生改变的时候,viewModel更新LiveData的value值,由LiveData的双向绑定机制通知UI更新。

    /**  添加viewModel绑定 **/
    private fun init(){
        viewModel = ViewModelProvider(
                this, ViewModelProvider.AndroidViewModelFactory(application)
        ).get(WeatherViewModel::class.java)
        //添加数据观察
        viewModel.weatherEvent.observe(this, Observer<WeatherNetData> { data -> binding.weatherData = data })
    }

在LiveData中并没有对外暴露的公共方法进行更新数据,可以使用MutableLiveData进行数据的更新,分别是setValue和postValue,他们之间的区别在于setValue是必须是在主线程的,而postValue可以是在子线程中使用。

     /**  LiveData初始化 **/
    var weatherEvent = MutableLiveData<WeatherNetData>()

    /**  获取天气数据 **/
    open fun requestWeather(city:String){
        WeatherModel().request(object :OnGetDataListener<WeatherNetData>{
            override fun fail(response: WeatherNetData?, msg: String?) {
                //数据更新
                //主线程
                weatherEvent.value = response
                //子线程
                weatherEvent.postValue(response)
            }
            override fun success(response: WeatherNetData?) {
                //数据更新
                weatherEvent.value = response
            }
        },city)
    }

MVVM实例

为了更好的展示DataBinding与LiveData实现MVVM的模式,这里用一个简单小例子来辅助说明一下。


Demo示例

首先我们来明确一下Demo的功能,这是一个通过输入城市名查询城市天气的简单应用,在MVVM划分中,Model层应当包含网络请求以及网络请求的数据Bean,View层应当包括一个EditText控件和两个TextView控件,ViewModel中通过LiveData实现数据与视图的双向绑定。


MVVM.png

Model

Model层内部是一个网络请求,通过接口回调将请求结果返回给ViewModel,如下:

/**
 * @author : YangHaoYi on  2019/6/4.
 * Email  :  yang.haoyi@qq.com
 * Description : 数据Model
 * Change : YangHaoYi on  2019/6/4.
 * Version : V 1.0
 */
class WeatherModel : AbstractBaseModel() {

    /**  天气网络请求 **/
    fun request(listener:OnGetDataListener<WeatherNetData>,city:String){
        val response = api.getWeatherData(city)
        response.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(object :Observer<WeatherNetData>{
                    override fun onError(e: Throwable?) {
                        listener.fail(null,"")
                    }
                    override fun onNext(t: WeatherNetData?) {
                        listener.success(t)
                    }
                    override fun onCompleted() {
                    }
                })
    }
}

ViewModel

ViewModel层内部通过LiveData实现对View数据改变的通知

open class WeatherViewModel : ViewModel(){

    var weatherEvent = MutableLiveData<WeatherNetData>()

    /**  获取天气数据 **/
    open fun requestWeather(city:String){
        WeatherModel().request(object :OnGetDataListener<WeatherNetData>{
            override fun fail(response: WeatherNetData?, msg: String?) {
                //主线程
                weatherEvent.value = response
            }
            override fun success(response: WeatherNetData?) {
                weatherEvent.value = response
            }
        },city)
    }
}

View

View中收到更新状态后通过DataBinding实现对UI的更新

class MainActivity : FragmentActivity() {

    lateinit var binding: ActivityMainBinding
    lateinit var viewModel:WeatherViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //初始化DataBinding
        binding =DataBindingUtil.setContentView(this,R.layout.activity_main)
        binding.presenter = WeatherPresenter()
        init()
    }

    /**  添加viewModel绑定 **/
    private fun init(){
        viewModel = ViewModelProvider(
                this, ViewModelProvider.AndroidViewModelFactory(application)
        ).get(WeatherViewModel::class.java)
        //添加数据观察
        viewModel.weatherEvent.observe(this, Observer<WeatherNetData> { data -> binding.weatherData = data })
    }
}

示例源码

文章对通过DataBinding与LiveData实现的MVVM功能展示Demo已上传至GitHub,感兴趣的朋友可以clone下来共同探讨学习一下。
GitHub源码:Java版本链接

GitHub源码:Kotlin版本链接

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

推荐阅读更多精彩内容