不要忘记View Model!(翻译)

1、背景

最近,我看了许多在android方面的文章,比如androiddev and Android Weekly.这些文章是非常精彩的。坦白的说,一些年以前我从Window Phone开发转入到Android开发,我感觉到构造一个稳定可靠的app是非常困难的。Google的一些example也违反的最佳原则[1]。因此我想从我这些年.net的平台开发经验中写自己的MVVM版本(或者MVPVM)。看了Hannes Dorfmann’s发布的MVP框架在他的Mosby 中,我知道我不是唯一一个寻求稳定模式的开发者(他花了3年时间),Android SDK并不是你唯一学习架构途径。比如Fragment中的一些问题,受到Square Inc的反对 。

无论如何,这都应该朝着一个良好的UI框架发展,这是常识。架构。架构[2]对我们是有帮就像Dorfmann’s 和 Artem Zin’s发表的模型。然而,在Android中很少听到View Model-这真的不应该为不知道它而找什么借口。View Model 这一概念是非常好的在任何的MVC/MVP的模型中,在这篇文章中,我将解释如何把你的模型分成两个甚至三个的模型。

[1]比如网络框架,View,Model和Presenter的代码全部在一个文件中,并且在内部类中的任务都依赖于activity,在解析xml文件的时候也有一些错误。因此我们是否需要第三方的库来简化我们的xml解析和http通信。
[2]除了一些框架库外,还有一些小的库或者微型框架比如:Dagger, Flow, Mortar, Parceler, Icepick 和 RxAndroid都为MVP架构铺平了道路

2、不同的模型类型

VIEW MODEL

在这篇文章中我将描述一个松散的模型去映射具体的接口。Martin Fowler是Presentation的模型。这不同于商业模式它在一个特定的上下文中过滤、合并或转换的数据。之后,你可以有多个View Models给一个商业模式或者多个商业模式对就一个View Model。微软MVVM模式是紧耦合的业务模型,通过数据绑定。这不是我定义和使用的要求,如果这是你的项目的要求,它也肯定可以实现。

THE BUSINESS MODEL(业务或者商务模型)

我为什么命名了Business Model,它实际上是一个商业模式。想想在网上商店系统中的用户、产品、供应商或订单。在Android中代码中它包含了强类型的对象和与这些工作的商业模式。

THE TRANSPORT MODEL(传输模型)

我将介绍第三种模型,我称之为The Transport Model你不会总是需要它,即使你这样做,你不会总是把它作为一个单独的类。THE TRANSPORT MODEL是代表在运输中的商业模式的模型。如果你有一个REST service,它将代表JSON数据的类。单独作为一个独立的类是非常有用的,因为它允许我单元测试分析容易,以及允许切换传输协议尽可能小的冲突。这种模式工作非常好比如Gson或Jackson库。

3、Example

我现在展示一个电影数据库的app(以前听过吗)。这app是一个集合的json数据,我们需要请求数据并且显示在列表中,下面的是json数据:

{    "v" : 1,    "data" : [{            "released" : "2011-08-04",            "title" : "Hell on Wheels",            "category" : "TV series",            "id" : "tt1699748",            "stars" : "Anson Mount, Colm Meaney",            "image" : [                "http://ia.media-imdb.com/images/M/MV5BMTQ5NTE5NTYzMF5BMl5BanBnXkFtZTgwOTc4OTY0MzE@._V1_.jpg",                1297,                1404            ]        },        {            "released" : "2014-04-02",            "title" : "Hello Ladies: The Movie",            "category" : "TV movie",            "id" : "tt3762944",            "stars" : "Stephen Merchant, Christine Woods",            "image" : [                "http://ia.media-imdb.com/images/M/MV5BMTQ5MjYxMjkwOV5BMl5BanBnXkFtZTgwODE3MjY0MzE@._V1_.jpg",                1012,                1500            ]        }    ]}

这个json数据我想说明一个典型的问题:数据中包含一些不必要的对象(版本号),你不想要这些不必要的数据在你的app商业模型中,但希望日期、图像和星星列表作为强类型数据对象,所以创建了一组与业务模型分离的传输模型:

public class MovieJsonModel {    public String released;    public String title;    public String category;    public String id;    public String stars;    public ArrayList image;}public class MovieListJsonModel {    String v; // Version    public ArrayList data;}   

上面的类只需要一行代码用Gson去解析:
MovieListJsonModel transportModel = new GsonBuilder().fromJson(jsonString, MovieListJsonModel.class);

商业模式看起来有所不同:

public class MovieModel {    public String title;    public String id;    public Uri imageUri;    public Date releaseDate;    public ArrayList stars;    // Just illustrating that the model can and should contain business logic,     // not only data.    public boolean isReleased() {        return releaseDate != null && releaseDate.before(new Date());    }}

(创建一个列表只实例化了ArrayList)
因此,在这里,看到传输模型中的许多字符串字段都得到了一个强类型的对应字段。日期,图像URI星星列表实际上是一个ArrayList。我没有包含在这里的转换代码,但我会建议把它放在传输模型类。这样,您将不会引入从业务模型到传输协议的依赖关系。
最后一个视图模型如何看的例子:

public class MovieViewModel {    public String title;    public String releaseDate;    public Bitmap image;    public int backgroundColor;    public boolean isSelected;}

这样日期仍然是以字符串的方式表示,想要控件一个java对象,我们创建了一个View Model,仍然可以进行单元测试。此外,们如果要加载一个图片,以前使用的都是一个网址。这当然要求我们实际加载的是图片。我不太在确定在视图模型或业务模型这些代码应该写在哪里。

最后,可能有专门的字段给view,比如背景颜色,状态选择。也有可能从其他Business Models中获取数据,但我这个example中没有包含。

保持状态

View Model另一个好处是保存你的状态在模型是。比如你的系统进程被杀死,或者手机旋转的时候都要这样做。Parceler框架使这个变得简单的,但我们仍然需要一些调整,下面是没有打包的图片对象。

@Parcelpublic class MovieViewModel {    public String title;    public String releaseDate;    @Transient public Bitmap image;     public int backgroundColor;    public boolean isSelected;    // Reference properties:    public String imageUrl;      // A service client for downloading images asynchronously w/Rx    private ImageService mImageService; }

现在包含一个图片的URL字符串。使用RxAndroid加载图片并回调代码如下:

public Observable loadDataAsync() {    if (image != null) return Observable.from(new Void[0]);    else {        Observable imageObs = mImageService.loadImageAsync(movieModel.imageUri);        Observable doneObs = imageObs            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .map(new Func1() {                @Override                public Void call(Bitmap bitmap) {                    image = bitmap;                    return null;                }            });        return doneObs;    }}

View,Fragment,或者Activity在onResume()方法中加载图片,在onCreate()方法中确保进View Model已经初始化:

public class MovieActivityprivate MovieViewModel mViewModel;@Overridepublic void onCreate() {    super.onCreate(Bundle savedInstanceState);    if (savedInstanceState != null) {        mViewModel = (MovieViewModel) savedInstanceState.getParcelable("ViewModel");    }}@Overridepublic void onSaveInstanceState(Bundle outState) {     outState.putParcelable("ViewModel", mViewModel);     super.onSaveInstanceState(outState);}@Overridepublic void onResume() {    super.onResume();     if (mViewModel != null) {         loadAndBindViewModel();     }     else {         // Load the models asynchronously from the REST Service,          // then call loadAndBindViewModel()     } } private void loadAndBindViewModel() {        mViewModel.loadDataAsync().subscribe(new Action1() {         @Override         public void call(Void dummy) {             modelToUi();         }     }); } private void modelToUi() {     // Map all View Model properties to actual view controls }

一些情况下,可能需要Business Model 更好,比如你做本地缓存数据,这时候可能不想保存它到绑定的Activity或者Fragment中,而是保存到Shared Preferences,SD卡或者数据库中。在这篇文章是我不会详细的介绍。

4、总结

MVC/MVP内部的模型,我都已经涉及到了,但坚持View Model 模式,就会单独的表示传输,保持,业务和表示层。创建一个单独的视图模型类将确保不会将相关的东西放在业务层中,或将相关字段插入到业务或视图层中。坚持ViewModel类再绑定Parceler能够保留视图的状态即使设备旋转或进程被杀死。

推荐观看原地址
http://tech.vg.no/2015/04/06/dont-forget-the-view-model/

更多相关文章

  1. Android直接连接数据库服务器
  2. Android(安卓)拍照或从相册取图片并裁剪
  3. Android中WebView拦截js请求
  4. Android中AsyncTask异步加载
  5. Android中ListView 控件与 Adapter 适配器如何使用?
  6. Android应用架构之MVP模式
  7. [置顶] 【Android(安卓)Training】置顶索引
  8. Android(安卓)Input Framework(二)---EventHub
  9. Android的swift语言-Kotlin(一)

随机推荐

  1. Mysql中replace与replace into的用法讲解
  2. 怎样正确创建MySQL索引的方法详解
  3. MySQL存储引擎InnoDB的配置与使用的讲解
  4. MySQL通过show processlist命令检视性能
  5. Mysql查看最大连接数和修改最大连接数的
  6. 实现数据库水平切分的两个思路
  7. select count()和select count(1)的区别
  8. MySQL数据库大小写敏感的问题
  9. 浅谈mysql 系统用户最大文件打开数限制
  10. 分组查询GROUP BY的使用与SQL执行顺序的