不要忘记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/