Android MVVM ViewModel

不要忘记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<Object> image;
}

public class MovieListJsonModel {
    String v; // Version
    public ArrayList<MovieJsonModel> 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<String> 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框架使这个变得简单的,但我们仍然需要一些调整,下面是没有打包的图片对象。

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

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

public class MovieActivity
private MovieViewModel mViewModel;

@Override
public void onCreate() {
    super.onCreate(Bundle savedInstanceState);
    if (savedInstanceState != null) {
        mViewModel = (MovieViewModel) savedInstanceState.getParcelable("ViewModel");
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
     outState.putParcelable("ViewModel", mViewModel);
     super.onSaveInstanceState(outState);
}

@Override
public 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<Void>() {
         @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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值