MVVM最佳实践

一、什么是MVVM

MVP中随着业务逻辑的增加,UI的改变多的情况下,会有非常多的跟UI相关的情况,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题,通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。

MVVM最佳实践_第1张图片

MVVM 由下面三层组成:

  1. View:主要进行视图控件的一些初始设置,不应该有任何的数据逻辑操作。
  2. Model:定义实体类,以及获取业务数据模型,比如通过数据库或者网络来操作数据等。
  3. ViewModel:作为连接 View 与 Model 的中间桥梁,ViewModel 与 Model 直接交互,处理完业务逻辑后,通过 DataBinding 将数据变化反应到用户界面上。

二、MVVM需要哪些知识

1、DataBinding

MVVM 是一种思想,一种架构模式,而 DataBinding 是谷歌推出的方便实现 MVVM 的工具。在 DataBinding 库之前,我们经常会写一些重复性很高而且毫无营养的代码,比如:findViewById()、setText()、setOnClickListener() 等。直到2015谷歌 I/O大会推出了 DataBinding,一个实现视图和数据双向绑定的工具。使用 DataBinding 库以后,可以使用声明式布局文件来减少粘结业务逻辑和布局文件的胶水代码,有利于开发者更方便地实现 MVVM 模式。

DataBinfing的使用详细见下面文章:

Android DataBinding使用详解

2、LiveData和ViewModel

LiveData和ViewModel是Android Architecture Components中推出的类。使用详细见下面文章:

Android Architecture Components

三、使用实践

下面使用MVVM实现一个天气查询的例子。天气查询的api使用聚合数据提供的api。

1、添加对DataBinding的支持

在build.gradle的android下添加如下代码:

    dataBinding{
        enabled true
    }

2、定义Model

1)、定义天气信息的实体类

public class WeatherInfo {
    private String reason;
    private ResultBean result;
    private int error_code;

    public static class ResultBean {
        private String city;
        private RealtimeBean realtime;
        private List future;

        
        public static class RealtimeBean {
            private String temperature;
            private String humidity;
            private String info;
            private String wid;
            private String direct;
            private String power;
            private String aqi;           
        }

        public static class FutureBean {
            private String date;
            private String temperature;
            private String weather;
            private WidBean wid;
            private String direct;
          
            public static class WidBean {
                private String day;
                private String night;
              
            }
        }
    }
}

为了节约篇幅我省略了get set方法。

2)、定义WeatherModel

天气Model用于联机获取天气信息,这里使用Retrofit和RxJava进行Http请求,具体实现这里不介绍。由于获取天气联机是异步操作,我们需要定义接口回调请求结果。接口定义如下:

public interface GetRemoteListener {
    void onSuccess(T t);
    void onError(String msg);
}

WeatherModel的定义如下:

public class WeatherModel {
    public void getWeatherInfo(String city, final GetRemoteListener listener) {
        HttpHelper.getWeatherInfoApi(city).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(WeatherInfo value) {
                        listener.onSuccess(value);
                    }

                    @Override
                    public void onError(Throwable e) {
                        listener.onError(e.getMessage());
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}

3、定义ViewModel

定义需要和用到的对象。这些对象都是用于界面展示的。

/**
 * 后台返回查询天气实体对象
 */
private MutableLiveData weatherInfo = new MutableLiveData<>();
/**
 * 用户数据查询天气的城市
 */
private MutableLiveData inputCity = new MutableLiveData<>();
/**
 * 界面提示的错误信息
 */
private MutableLiveData errMsg = new MutableLiveData<>();
/**
 * 是否正在联机标志。其值改变时通过代码控制显示和隐藏Dialog
 */
private MutableLiveData isLoading = new MutableLiveData<>();

并且要为上面这些变量设置Getter方法,否则在布局文件中不能获取上面的变量。

定义获取天气信息的方法:

public void getWeatherWithNet(){
    beforeOnlineHandle();

    weatherModel.getWeatherInfo(inputCity.getValue(), new GetRemoteListener() {
        @Override
        public void onSuccess(WeatherInfo weatherInfo) {
            if(weatherInfo.getError_code() == 0) {
                WeatherViewModel.this.weatherInfo.setValue(weatherInfo);
            }else{
                errMsg.setValue(weatherInfo.getReason());
            }
            isLoading.setValue(false);
        }

        @Override
        public void onError(String msg) {
            errMsg.setValue(msg);
            isLoading.setValue(false);
        }
    });
}

public void beforeOnlineHandle(){
    isLoading.setValue(true);
    errMsg.setValue(null);
}

4、定义View

这里就是定义Activity和其布局文件。注意android中的View包含布局文件中的和代码生成的View两部分。

1)、定义布局文件

布局文件最外层是layout,其中包含data和布局文件。data定义数据,布局文件定义布局。



    
        
        
    

    
        
        

        

上面需要注意的点:

  1. 加一个=就可以实现双向绑定。android:text="@={viewModel.inputCity}"
  2. 在ViewModel中定义一个bool类型的变量,可以控制界面某个控件的显示与隐藏,比如错误提示界面
  3. as xml中表示且&&会被认为是特殊字符,所以&&应该写成&&

2)、定义Activity

代码如下,具体说明见注释。

public class WeatherActivity extends AppCompatActivity implements View.OnClickListener {
    private WeatherViewModel viewModel;
    private ActivityWeatherBinding dataBinding;
    private MaterialDialog loadingDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViewModel();
        initDataBinding();
        setListener();
    }

    // 说明1
    private void initViewModel() {
        // 获取ViewModel,屏幕旋转不是新建,只是重连
        viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())
        ).get(WeatherViewModel.class);
        // 使用LiveData的功能,当isLoading值改变时回调这里代码控制界面的显示
        viewModel.getIsLoading().observe(this, new Observer() {
            @Override
            public void onChanged(Boolean aBoolean) {
                if (aBoolean) {
                    showLoadingDialog();
                } else {
                    dismissLoadingDialog();
                }
            }
        });
    }

    // 说明2
    private void initDataBinding() {
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_weather);
        dataBinding.setViewModel(viewModel);
        // 必须设置
        dataBinding.setLifecycleOwner(this);
    }

    private void setListener() {
        findViewById(R.id.btn_request).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_request:
                viewModel.getWeatherWithNet();
                break;
        }
    }

    /**
     * 代码显示Dialog
     */
    private void showLoadingDialog() {
        if (loadingDialog == null) {
            loadingDialog = new MaterialDialog.Builder(this)
                    .content("拼命加载中...")
                    .progress(true, 0)
                    .build();
        }

        loadingDialog.show();
    }

    /**
     * 代码隐藏Dialog
     */
    private void dismissLoadingDialog() {
        loadingDialog.dismiss();
    }
}

四、注意点

1、xml定义的控件控制是否显示在ViewModel中定义一个bool类型变量控制

2、代码定义控件控制是否显示在代码中使用viewModel.getIsLoading().observe(this, new Observer() {}注册观察控制

你可能感兴趣的:(MVVM最佳实践)