Android DataBinding & MVVM

作者:荣华谢后

地址:http://www.jianshu.com/p/314f203459f2

声明:本文是荣华谢后原创,已获其授权发布,未经原作者允许请勿转载

先说说两者的关系,DataBinding是一个实现数据和UI绑定的框架,而MVVM是一种架构模式,实现MVVM模式需要借助DataBinding来完成。

本文将以《Android 谈谈我所理解的MVP》中的例子为基础,使用MVVM模式重新实现一下。

嗯,本篇博客也是有副标题的:Retrofit + RxJava + RxLifecycle + MVVM

本文涉及到的一些基础知识:

Android DataBinding使用详解(一):

http://www.jianshu.com/p/e4c4a9aece40

Android DataBinding使用详解(二):

http://www.jianshu.com/p/895cd56e3fd8

Android Retrofit + RxJava使用详解:

http://www.jianshu.com/p/021a2c6e128b

Android 使用RxLifecycle解决RxJava内存泄漏:

http://www.jianshu.com/p/7fae42861b8d

Demo效果图:

Demo下载地址:https://github.com/alidili/Demos/tree/master/MVVMDemo

先来张图感受一下:

MVVM

  • View View层只负责UI相关的工作,不进行逻辑处理,并且不需要在Activity/Fragment中做更新UI的操作,更新UI通过Binding实现,在ViewModel中更新数据源即可。如果UI和业务逻辑没有关系,比如点击按钮显示或隐藏控件,是可以在Activity/Fragment中进行UI更新的。
  • ViewModel ViewModel层只做和逻辑处理相关的工作,在ViewModel中不会持有View层的引用,双方通过Binding方式通信,只需要在ViewModel层对数据进行操作,View层就会自动更新UI。
  • Model Model层的职责和MVC、MVP类似,基本上就是实体类(Bean)和Retrofit的Service。 在MVVM中Bean是继承BaseObservable的,有些文章里把Bean归到了ViewModel层中,因为Bean中实现的观察者模式才是View和Model沟通的桥梁,嗯,有道理,不过我还是想把它归到Model层,关于这个问题不在过多讨论,仁者见仁,智者见智吧。

MVVM实践

首先看下项目结构:

做一些准备工作

定义一个请求参数接口:

public interface RetrofitService {

    /**
     * 获取快递信息
     * Rx方式
     *
     * @param type   快递类型
     * @param postid 快递单号
     * @return Observable<ExpressInfo>
     */
    @GET(Constant.UrlOrigin.get_express_info)
    Observable<ExpressInfo> getExpressInfoRx(@Query("type") String type, @Query("postid") String postid);
}
定义Retrofit帮助类,用于Retrofit与RetrofitService的初始化:
public class RetrofitHelper {

    private static RetrofitHelper retrofitHelper;
    private RetrofitService retrofitService;

    public static RetrofitHelper getInstance() {
        return retrofitHelper == null ? retrofitHelper = new RetrofitHelper() : retrofitHelper;
    }

    private RetrofitHelper() {
        // 初始化Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Constant.SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create()) // json解析
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
                .build();
        retrofitService = retrofit.create(RetrofitService.class);
    }

    public RetrofitService getRetrofitService() {
        return retrofitService;
    }
}
Model层
Retrofit相关:
public class DataManager {


    private static DataManager dataManager;
    private RetrofitService retrofitService;

    public static DataManager getInstance() {
        return dataManager == null ? dataManager = new DataManager() : dataManager;
    }

    private DataManager() {
        retrofitService = RetrofitHelper.getInstance().getRetrofitService();
    }

    /**
     * 获取快递信息
     *
     * @param type   快递类型
     * @param postid 快递单号
     * @return Observable<ExpressInfo>
     */
    public Observable<ExpressInfo> getExpressInfo(String type, String postid) {
        return retrofitService.getExpressInfoRx(type, postid);
    }
}

使用了单例模式,在构造方法中获取RetrofitService实例,定义getExpressInfo方法,返回泛型为ExpressInfo的被观察者对象,稍后在ViewModel中会用到。

实体类:

public class ExpressInfo extends BaseBean {


    private String message;
    private String nu;
    private String ischeck;
    private String condition;
    private String com;
    private String status;
    private String state;
    private List<DataBean> data;

    public void setExpressInfo(ExpressInfo expressInfo) {
        setMessage(expressInfo.getMessage());
        setNu(expressInfo.getNu());
        setIscheck(expressInfo.getIscheck());
        setCondition(expressInfo.getCondition());
        setCom(expressInfo.getCom());
        setStatus(expressInfo.getStatus());
        setState(expressInfo.getState());
        setData(expressInfo.getData());
    }

    @Bindable
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        notifyPropertyChanged(BR.message);
    }

    @Bindable
    public String getNu() {
        return nu;
    }

    public void setNu(String nu) {
        this.nu = nu;
        notifyPropertyChanged(BR.nu);
    }
 
    ...
}

BaseBean:

public class BaseBean extends BaseObservable {

}

主要看下setExpressInfo方法,setExpressInfo方法是为了在ViewModel层中获取到数据之后,方便更新数据源的,其中的操作也可以放在ViewModel中进行处理。

ViewModel层

首先看下BaseViewModel:

public class BaseViewModel {


    private LifecycleProvider<ActivityEvent> provider;

    public BaseViewModel(LifecycleProvider<ActivityEvent> provider) {
        this.provider = provider;
    }

    public LifecycleProvider<ActivityEvent> getProvider() {
        return provider;
    }
}

由于使用了RxLifecycle框架来管理RxJava,而RxLifecycle与RxJava的绑定是在ViewModel中进行的,所以就需要在构造ViewModel时传入LifecycleProvider<ActivityEvent>接口的实例。MainActivity最终继承了RxAppCompatActivity,在RxAppCompatActivity内部又实现了LifecycleProvider<ActivityEvent>接口,所以在构造ViewModel时直接传入this就可以了。

public class ExpressViewModel extends BaseViewModel {


    public ExpressInfo expressInfo;
    private DataManager dataManager;

    // 是否显示Loading
    public final ObservableBoolean isShowLoading = new ObservableBoolean();
    // 错误信息
    public final ObservableField<String> errorMessage = new ObservableField<>();

    public ExpressViewModel(LifecycleProvider<ActivityEvent> provider, ActivityMainBinding binding) {
        super(provider);
        expressInfo = new ExpressInfo();
        binding.setExpressViewModel(this);
        dataManager = DataManager.getInstance();
    }

    /**
     * 获取快递信息
     *
     * @param type   快递类型
     * @param postid 快递单号
     */
    public void getExpressInfo(String type, String postid) {
        isShowLoading.set(true);

        dataManager.getExpressInfo(type, postid)
                .subscribeOn(Schedulers.io()) // 在子线程中进行Http访问
                .observeOn(AndroidSchedulers.mainThread()) // UI线程处理返回接口
                .compose(getProvider().<ExpressInfo>bindUntilEvent(ActivityEvent.DESTROY)) // onDestroy取消订阅
                .subscribe(new DefaultObserver<ExpressInfo>() {  // 订阅
                    @Override
                    public void onNext(@NonNull ExpressInfo expressInfo) {
                        ExpressViewModel.this.expressInfo.setExpressInfo(expressInfo);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        errorMessage.set(e.getMessage());
                        isShowLoading.set(false);
                    }

                    @Override
                    public void onComplete() {
                        isShowLoading.set(false);
                    }
                });
    }
}

首先看下:

// 是否显示Loading

public final ObservableBoolean isShowLoading = new ObservableBoolean(); // 错误信息 public final ObservableField<String> errorMessage = new ObservableField<>();

由于Dialog和Toast不是定义在布局文件中的,所以不能通过Binding的方式显示Dialog和Toast,而Activity中不能处理逻辑,所以无法在Activity中判断是否显示Dialog和Toast,在ViewModel中又不能显示UI,那该怎么办呢?别着急,可以在ViewModel中定义一个观察者对象isShowLoading,在Activity中对isShowLoading进行监听,true时显示Dialog,false时关闭Dialog就可以了,errorMessage同理。

然后看下构造方法:

public ExpressViewModel(LifecycleProvider<ActivityEvent> provider, ActivityMainBinding binding) {

    super(provider);
    expressInfo = new ExpressInfo();
    binding.setExpressViewModel(this);
    dataManager = DataManager.getInstance();
}

在构造方法中传入LifecycleProvide和Binding的实例,调用Bindin的setExpressViewModel方法将ViewModel层与View层进行绑定。

接下来定义一个getExpressInfo方法,在其中调用DataManager类的getExpressInfo方法(根据实际需求命名),返回被观察者对象,然后进行订阅,在onNext方法中设置数据,在onError和onComplete方法中设置Dialog和Toast的状态。

View层

布局文件:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="expressViewModel"
            type="com.yl.mvvmdemo.viewmodel.ExpressViewModel" />

        <variable
            name="clickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:text="@{expressViewModel.expressInfo.data[0].context}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:onClick="@{clickListener}"
            android:text="获取快递信息" />

    </RelativeLayout>

</layout>

定义变量expressViewModel,会在Binding中生成对应的set方法,就是我们在ViewModel的构造方法中调用的setExpressViewModel方法,然后在TextView中显示快递信息。

Activity:

public class BaseActivity extends RxAppCompatActivity {

}public class MainActivity extends BaseActivity {

    private ProgressDialog progressDialog;
    private ExpressViewModel expressViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        expressViewModel = new ExpressViewModel(this, binding);
        binding.setClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expressViewModel.getExpressInfo("yuantong", "11111111111");
            }
        });

        // 显示Loading
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("正在获取快递信息...");
        expressViewModel.isShowLoading.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable observable, int i) {
                if (expressViewModel.isShowLoading.get()) {
                    progressDialog.show();
                } else {
                    progressDialog.dismiss();
                }
            }
        });

        // 显示错误信息
        expressViewModel.errorMessage.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable observable, int i) {
                Toast.makeText(MainActivity.this, expressViewModel.errorMessage.get(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

BaseActivity:

在Activity中创建ExpressViewModel对象,在点击方法中调用getExpressInfo方法获取快递信息,重点看下下面的方法:

expressViewModel.isShowLoading.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

    @Override
    public void onPropertyChanged(Observable observable, int i) {
        if (expressViewModel.isShowLoading.get()) {
            progressDialog.show();
        } else {
            progressDialog.dismiss();
        }
    }
});

对isShowLoading添加一个属性改变的监听,如果为true就显示loading框,为false就关闭。

到这里,MVVM模式就学习完了,DataBinding确实很强大,但有个致命的问题,就是出现问题的时候不好debug,因为UI更新都在布局文件中完成了,而且不论出现什么错误,都会提示找不到Binding文件,没有具体的提示,这个相当头疼。

源码已托管到GitHub上,欢迎Fork,觉得还不错就Start一下吧!

GitHub传送门:https://github.com/alidili/Demos/tree/master/MVVMDemo

欢迎同学们吐槽评论,如果你觉得本篇博客对你有用,那么就留个言或者点下喜欢吧(^-^)

原文发布于微信公众号 - Android先生(cyg_24kshign)

原文发表时间:2017-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android干货

APP级别处理未捕获异常

35460
来自专栏项勇

笔记56 | 管理网络的使用

17160
来自专栏知识分享

8-51单片机ESP8266学习-AT指令(测试TCP服务器--51单片机程序配置8266,做自己的手机TCP客户端发信息给单片机控制小灯的亮灭)

http://www.cnblogs.com/yangfengwu/p/8776712.html 先把源码和资料链接放到这里 链接:https://pan.ba...

64020
来自专栏jianhuicode

如何使用MVP+Dagger2+RxJava+Retrofit开发(1)

概述 在2016年5,6月份开始在知乎上看到开发方法,那时候记得是看mvc,mvp,mvvm这三种开发模式区别,后面进一步了解到google在github上开源...

52780
来自专栏CodingBlock

Android学习笔记(四)深入探讨Activity

  在应用程序中至少包含一个用来处理应用程序的主UI功能的主界面屏幕。这个主界面一般由多个Fragment组成,并由一组次要Activity支持。要在屏幕之间切...

236100
来自专栏知识分享

Android之TCP服务器编程android 之TCP客户端编程

推荐一个学java或C++的网站http://www.weixueyuan.net/,本来想自己学了总结出来再写博客,现在没时间,打字太慢!!!!,又想让这好东...

89760
来自专栏GIS讲堂

安卓中的数据存取

在我们每天使用的手机设备中,无时无刻都在进行着数据的存和取。安卓作为一种手机操作系统,提供了如下方式的数据存取方式:Preference,File,Sqlite...

13520
来自专栏KK的小酒馆

APP启动引导页的制作,用ViewPager实现翻页动画

这次制作App的引导页,主要用到2个知识“SharedPreferences 和 ViewPager”

49310
来自专栏Android先生

Context都没弄明白,还怎么做Android开发?

作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一...

9820
来自专栏向治洪

顺序广播和无序广播

普通广播(Normal Broadcast): 一,优缺点:和有序广播的优缺点相反! 二,发送广播的方法:sendBroadcast() 有序广播(Or...

23590

扫码关注云+社区

领取腾讯云代金券