TShopping

 找回密碼
 註冊
搜索
TShopping 精選文章 Android 手機開發 查看內容

Android DataBinding:再見Presenter,你好ViewModel!

2016-10-20 01:26| 發佈者: woff| 查看: 11408| 評論: 1|原作者: woff

摘要: 最近一段时间MVP模式已经成为Android应用开发UI层架构设计的主流趋势。类似TED MOSBY,nucleus和mortar之类的框架都引入了Presenters来帮助我们搭建简洁的app架构。它们也(在不同的程度上)帮助我们处理Android平台 ...
 
最近一段时间MVP模式已经成为Android应用开发UI层架构设计的主流趋势。类似TED MOSBYnucleusmortar之类的框架都引入了Presenters来帮助我们搭建简洁的app架构。它们也(在不同的程度上)帮助我们处理Android平台上臭名昭著的设备旋转和状态持久化等问题。MVP模式也有助于隔离样板代码,虽然这并不是MVP模式的设计初衷。

在Google I/O 2015上,伴随着Android M预览版发布的Data Binding兼容函数库改变了这一切。
根据维基百科上关于MVP的词条描述,Presenter作用如下:

Presenter作用于model和view,它从仓库(Model)中获取数据,并格式化后让view进行显示。

Data Binding框架将会接管Presenter的主要职责(作用于model和view上),Presenter的其他剩余职责(从仓库中获取数据并进行格式化处理)则由ViewModel(一个增强版的Model)接管。ViewModel是一个独立的Java类,它的唯一职责是表示一个View后面的数据。它可以合并来自多个数据源(Models)的数据,并将这些数据加工后用于展示。我之前写过一篇关于ViewModel的短文,讲述了它与Data Model或者Transport Model之间的区别。

我们今天要讲述的架构是MVVM(Model-View-ViewModel),它最初是在2005年(不要吓到哦)由微软提出的一个被证明可用的概念。下面我将举例说明从MVP到MVVM的改变,容我盗用下Hanne Dorfmann在他介绍TED MOSBY框架的文章中的插图。
mvp.pngmvvm.png



可以看到对view中数据的所有绑定和更新操作都是通过Data Binding框架实现的。通过ObservableField类,View在model发生变化时会作出反应,在XML文件中对属性的引用使得框架在用户操作View时可以将变化推送给对应的ViewModel。我们也可以通过代码订阅属性的变化,这样可以实现例如当CheckBox被点击后,TextView被禁用这样的功能。像这样使用标准Java类来表示View的视觉状态的一个很大优势是明显的:你可以很容易对这种视觉行为进行单元测试。

上面关于MVP的插图中有一个名为Presenter.loadUsers()的方法,这是一个命令。在MVVM中这些方法定义在ViewModel中。从维基百科文章中可以看到:
view model是一个抽象的view,它对外暴露公有的属性和命令。

因此这可能跟你以前熟悉的东西有些不同。在MVP模式中models很可能只是纯粹用于保存数据的“哑”类。对于把业务逻辑放到Models或者View Models中的行为不要感到害怕。这是面向对象编程的核心准则。回到Presenter.loadUsers()函数,现在它是一个放在ViewModel中的函数,它可能被View的后置代码(code-behind)调用,或者被位于View的XML文件中的数据绑定命令调用。如果android-developer-preview问题跟踪里面这个issue描述的问题得到支持的话。如果我们没能得到数据绑定到命令功能的支持,那就只能使用以前的android:onClick语法,或者手动在view中添加监听器了。

代码后置(code-behind),微软的一个概念,经常与早期的ASP.NET或者WinForms联系在一起。我想它也可以作为Android上的一个描述术语,View由两个元素组成:View的布局文件(XML)和后置代码(Java),这通常是指Fragments,Activities或者继承自View.java的其他类。
处理系统调用View的后置代码还需要完成一系列用例-初始化系统,打开对话框的函数,或者任何需要引用Android Context对象的调用。但不要把这样的代码调用放到ViewModel中。如果ViewModel包含
  1. import android.content.Context;
複製代碼

这段代码,说明你用错了,千万不要这么做,好奇害死猫。

我还没有完全决定解决这个问题的最好办法,不过这是因为有几个好的选择。一个方法是通过在ViewModel中持有View的一个引用来保存Mosby中的presenter元素。这个方案不会降低可测试性。但跟在Mosby中持有一个单独的Presenter类不同,我坚持认为将View作为接口的具体实现可以起到简化代码的作用。另一个方法可能是使用Square的Otto之类的事件总线机制来初始化类似
  1. new ShowToastMessage("hello world")
複製代碼

的命令。这将会很好的分离view和viewmodel,不过这是一件好事吗?
我们不需要框架了吗?那么Data Binding框架已经接管了类似Mosby或者Mortar等框架的工作了吗?只是一部分。我希望看到的是这些框架进化或者新增分支变成MVVM类型的框架,这样我们在充分利用Data Binding的同时,可以最低限度依赖第三方框架,并保持框架的小而美。虽然Presenter的时代可能已经结束了,但这些框架在管理声明周期和view(或者ViewModel)的状态持久化方面还在发挥作用,这一点并没有改变。(如果Google引入一个LifeCycleAffected接口让Fragment, Activity 和 View进行实现,那将是多么酷的一件事!这个接口由一个名为addOnPauseListener()和addOnResumeListener()的函数,在我们例子中如何使用这个接口将留给你来实现。)
更新:最近了解到AndroidViewModel框架,它实际上可能很适合MVVM和Android的Data Binding。不过我还没有时间试用它。
总结当我首次听说Android M致力于改进SDK并重点关注开发者时,我真的很激动。当我听说他们引入了Data Binding,我被震惊了。在其他平台如WinForms, WPF, Silverlight 和 Windows Phone上面我已经用了好几年Data Binding技术。我知道这可以帮助我们写出简洁的架构和更少的样板代码。这个框架是站在开发者这边的,而不是阻碍我们的,很久以前我就感受到这一点了。
但Data Binding不是银弹,它也有缺点。在XML文件中定义绑定本身就是一个问题。XML不会被编译,它也不能进行单元测试。因此你将会经常在运行时才发现错误,而不是在编译期间。忘记将属性绑定到View了?很不幸。但工具可以发挥很大的帮助-这是为什么我希望Google能够尽量让Android Studio最大程度支持Data Binding。XML绑定的语法和引用检查,自动完成和导航支持。XML字段的重命名支持。从我测试Android Studio 1.3 beta来看,我至少可以肯定他们有在考虑这件事情。某些功能已经支持了,但还有很多没有支持,不过1.3版本仍然处于beta阶段,我们可以有更多的期待。
代码示例接下来我将给出一个示例,演示从MVP架构迁移到MVVM架构的结果。在MVP版本工程中,我使用Mosby框架并使用Butterknife实现视图注入。在MVVM例子中我使用Android M Data Binding并移除工程中对Mosby和Butterknife的依赖。结果是Presenter可以丢掉了,Fragment中代码减少了,不过ViewModel接管了很多代码。
在这个例子中我直接引用View来生成toast消息。这也许不是我以后提倡的一种方法, 但理论上这么做没什么问题。使用Robolectric和Mockito来对Fragment进行mock,这样是可测试的,而且不会泄露内存,除非你错误的引用了ViewModels。
下面这个app只是起一个演示的作用,它具有一个简单的登陆页面,后台会加载一些异步数据,views之间会有一些依赖。
illustration.png

如果你希望在Android Studio中阅读代码,可以到Github上分别检出MVP和MVVM的标签。
下面准备好接受代码轰炸吧

MVP – VIEW – XML

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.                 xmlns:tools="http://schemas.android.com/tools"
  3.                 android:layout_width="match_parent"
  4.                 android:layout_height="match_parent"
  5.                 android:paddingLeft="@dimen/activity_horizontal_margin"
  6.                 android:paddingRight="@dimen/activity_horizontal_margin"
  7.                 android:paddingTop="@dimen/activity_vertical_margin"
  8.                 android:paddingBottom="@dimen/activity_vertical_margin"
  9.                 tools:context=".MainActivityFragment">

  10.     <TextView
  11.         android:text="..."
  12.         android:layout_width="wrap_content"
  13.         android:layout_height="wrap_content"
  14.         android:layout_alignParentEnd="true"
  15.         android:id="@+id/loggedInUserCount"/>

  16.     <TextView
  17.         android:text="# logged in users:"
  18.         android:layout_width="wrap_content"
  19.         android:layout_height="wrap_content"
  20.         android:layout_alignParentEnd="false"
  21.         android:layout_toLeftOf="@+id/loggedInUserCount"/>

  22.     <RadioGroup
  23.         android:layout_marginTop="40dp"
  24.         android:id="@+id/existingOrNewUser"
  25.         android:gravity="center"
  26.         android:layout_width="wrap_content"
  27.         android:layout_height="wrap_content"
  28.         android:layout_centerHorizontal="true"
  29.         android:orientation="horizontal">

  30.         <RadioButton
  31.             android:layout_width="wrap_content"
  32.             android:layout_height="wrap_content"
  33.             android:text="Returning user"
  34.             android:id="@+id/returningUserRb"/>

  35.         <RadioButton
  36.             android:layout_width="wrap_content"
  37.             android:layout_height="wrap_content"
  38.             android:text="New user"
  39.             android:id="@+id/newUserRb"
  40.             />

  41.     </RadioGroup>

  42.     <LinearLayout
  43.         android:orientation="horizontal"
  44.         android:layout_width="match_parent"
  45.         android:layout_height="wrap_content"
  46.         android:id="@+id/username_block"
  47.         android:layout_below="@+id/existingOrNewUser">

  48.         <TextView
  49.             android:layout_width="wrap_content"
  50.             android:layout_height="wrap_content"
  51.             android:textAppearance="?android:attr/textAppearanceMedium"
  52.             android:text="Username:"
  53.             android:id="@+id/textView"
  54.             android:minWidth="100dp"/>

  55.         <EditText
  56.             android:layout_width="wrap_content"
  57.             android:layout_height="wrap_content"
  58.             android:id="@+id/username"
  59.             android:minWidth="200dp"/>
  60.     </LinearLayout>

  61.     <LinearLayout
  62.         android:orientation="horizontal"
  63.         android:layout_width="match_parent"
  64.         android:layout_height="wrap_content"
  65.         android:layout_alignParentStart="false"
  66.         android:id="@+id/password_block"
  67.         android:layout_below="@+id/username_block">

  68.         <TextView
  69.             android:layout_width="wrap_content"
  70.             android:layout_height="wrap_content"
  71.             android:textAppearance="?android:attr/textAppearanceMedium"
  72.             android:text="Password:"
  73.             android:minWidth="100dp"/>

  74.         <EditText
  75.             android:layout_width="wrap_content"
  76.             android:layout_height="wrap_content"
  77.             android:inputType="textPassword"
  78.             android:ems="10"
  79.             android:id="@+id/password"/>

  80.     </LinearLayout>

  81.     <LinearLayout
  82.         android:orientation="horizontal"
  83.         android:layout_width="match_parent"
  84.         android:layout_height="wrap_content"
  85.         android:layout_below="@+id/password_block"
  86.         android:id="@+id/email_block">

  87.         <TextView
  88.             android:layout_width="wrap_content"
  89.             android:layout_height="wrap_content"
  90.             android:textAppearance="?android:attr/textAppearanceMedium"
  91.             android:text="Email:"
  92.             android:minWidth="100dp"/>

  93.         <EditText
  94.             android:layout_width="wrap_content"
  95.             android:layout_height="wrap_content"
  96.             android:inputType="textEmailAddress"
  97.             android:ems="10"
  98.             android:id="@+id/email"/>
  99.     </LinearLayout>

  100.     <Button
  101.         android:layout_width="wrap_content"
  102.         android:layout_height="wrap_content"
  103.         android:text="Log in"
  104.         android:id="@+id/loginOrCreateButton"
  105.         android:layout_below="@+id/email_block"
  106.         android:layout_centerHorizontal="true"/>
  107. </RelativeLayout>
複製代碼

MVP – VIEW – JAVA

  1. package com.nilzor.presenterexample;

  2. import android.os.Bundle;
  3. import android.view.LayoutInflater;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.Button;
  7. import android.widget.CompoundButton;
  8. import android.widget.RadioButton;
  9. import android.widget.TextView;
  10. import android.widget.Toast;
  11. import com.hannesdorfmann.mosby.mvp.MvpFragment;
  12. import com.hannesdorfmann.mosby.mvp.MvpView;
  13. import butterknife.InjectView;
  14. import butterknife.OnClick;

  15. public class MainActivityFragment extends MvpFragment implements MvpView {
  16.     @InjectView(R.id.username)
  17.     TextView mUsername;

  18.     @InjectView(R.id.password)
  19.     TextView mPassword;

  20.     @InjectView(R.id.newUserRb)
  21.     RadioButton mNewUserRb;

  22.     @InjectView(R.id.returningUserRb)
  23.     RadioButton mReturningUserRb;

  24.     @InjectView(R.id.loginOrCreateButton)
  25.     Button mLoginOrCreateButton;

  26.     @InjectView(R.id.email_block)
  27.     ViewGroup mEmailBlock;

  28.     @InjectView(R.id.loggedInUserCount)
  29.     TextView mLoggedInUserCount;

  30.     public MainActivityFragment() {
  31.     }

  32.     @Override
  33.     public MainPresenter createPresenter() {
  34.         return new MainPresenter();
  35.     }

  36.     @Override
  37.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  38.         return inflater.inflate(R.layout.fragment_main, container, false);
  39.     }

  40.     @Override
  41.     public void onViewCreated(View view, Bundle savedInstanceState) {
  42.         super.onViewCreated(view, savedInstanceState);
  43.         attachEventListeners();
  44.     }

  45.     private void attachEventListeners() {
  46.         mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  47.             @Override
  48.             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  49.                 updateDependentViews();
  50.             }
  51.         });
  52.         mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  53.             @Override
  54.             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  55.                 updateDependentViews();
  56.             }
  57.         });
  58.     }

  59.     /** Prepares the initial state of the view upon startup */
  60.     public void setInitialState() {
  61.         mReturningUserRb.setChecked(true);
  62.         updateDependentViews();
  63.     }

  64.     /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
  65.     public void updateDependentViews() {
  66.         if (mReturningUserRb.isChecked()) {
  67.             mEmailBlock.setVisibility(View.GONE);
  68.             mLoginOrCreateButton.setText(R.string.log_in);
  69.         }
  70.         else {
  71.             mEmailBlock.setVisibility(View.VISIBLE);
  72.             mLoginOrCreateButton.setText(R.string.create_user);
  73.         }
  74.     }

  75.     public void setNumberOfLoggedIn(int numberOfLoggedIn) {
  76.         mLoggedInUserCount.setText(""  + numberOfLoggedIn);
  77.     }

  78.     @OnClick(R.id.loginOrCreateButton)
  79.     public void loginOrCreate() {
  80.         if (mNewUserRb.isChecked()) {
  81.             Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
  82.         } else {
  83.             Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
  84.         }
  85.     }
  86. }
複製代碼

MVP – PRESENTER

  1. package com.nilzor.presenterexample;

  2. import android.os.Handler;
  3. import android.os.Message;
  4. import com.hannesdorfmann.mosby.mvp.MvpPresenter;

  5. public class MainPresenter implements MvpPresenter {
  6.     MainModel mModel;
  7.     private MainActivityFragment mView;

  8.     public MainPresenter() {
  9.         mModel = new MainModel();
  10.     }

  11.     @Override
  12.     public void attachView(MainActivityFragment view) {
  13.         mView = view;
  14.         view.setInitialState();
  15.         updateViewFromModel();
  16.         ensureModelDataIsLoaded();
  17.     }

  18.     @Override
  19.     public void detachView(boolean retainInstance) {
  20.         mView = null;
  21.     }

  22.     private void ensureModelDataIsLoaded() {
  23.         if (!mModel.isLoaded()) {
  24.             mModel.loadAsync(new Handler.Callback() {
  25.                 @Override
  26.                 public boolean handleMessage(Message msg) {
  27.                     updateViewFromModel();
  28.                     return true;
  29.                 }
  30.             });
  31.         }
  32.     }

  33.     /** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
  34.     private void updateViewFromModel() {
  35.         if (mView != null && mModel.isLoaded()) {
  36.             mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
  37.         }
  38.     }
  39. }
  40. MVP – MODEL

  41. package com.nilzor.presenterexample;

  42. import android.os.AsyncTask;
  43. import android.os.Handler;
  44. import java.util.Random;

  45. public class MainModel {
  46.     public Integer numberOfUsersLoggedIn;
  47.     private boolean mIsLoaded;
  48.     public boolean isLoaded() {
  49.         return mIsLoaded;
  50.     }

  51.     public void loadAsync(final Handler.Callback onDoneCallback) {
  52.         new AsyncTask() {
  53.             @Override
  54.             protected Void doInBackground(Void... params) {
  55.                 // Simulating some asynchronous task fetching data from a remote server
  56.                 try {Thread.sleep(2000);} catch (Exception ex) {};
  57.                 numberOfUsersLoggedIn = new Random().nextInt(1000);
  58.                 mIsLoaded = true;
  59.                 return null;
  60.             }

  61.             @Override
  62.             protected void onPostExecute(Void aVoid) {
  63.                 onDoneCallback.handleMessage(null);
  64.             }
  65.         }.execute((Void) null);
  66.     }
  67. }
  68. MVVM – VIEW – XML

  69. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  70.     xmlns:tools="http://schemas.android.com/tools">
  71.     <data>
  72.         <variable name="data" type="com.nilzor.presenterexample.MainModel"/>
  73.     </data>
  74.     <RelativeLayout
  75.         android:layout_width="match_parent"
  76.         android:layout_height="match_parent"
  77.         android:paddingLeft="@dimen/activity_horizontal_margin"
  78.         android:paddingRight="@dimen/activity_horizontal_margin"
  79.         android:paddingTop="@dimen/activity_vertical_margin"
  80.         android:paddingBottom="@dimen/activity_vertical_margin"
  81.         tools:context=".MainActivityFragment">

  82.         <TextView
  83.             android:text="@{data.numberOfUsersLoggedIn}"
  84.             android:layout_width="wrap_content"
  85.             android:layout_height="wrap_content"
  86.             android:layout_alignParentEnd="true"
  87.             android:id="@+id/loggedInUserCount"/>

  88.         <TextView
  89.             android:text="# logged in users:"
  90.             android:layout_width="wrap_content"
  91.             android:layout_height="wrap_content"
  92.             android:layout_alignParentEnd="false"
  93.             android:layout_toLeftOf="@+id/loggedInUserCount"/>

  94.         <RadioGroup
  95.             android:layout_marginTop="40dp"
  96.             android:id="@+id/existingOrNewUser"
  97.             android:gravity="center"
  98.             android:layout_width="wrap_content"
  99.             android:layout_height="wrap_content"
  100.             android:layout_centerHorizontal="true"
  101.             android:orientation="horizontal">

  102.             <RadioButton
  103.                 android:layout_width="wrap_content"
  104.                 android:layout_height="wrap_content"
  105.                 android:text="Returning user"
  106.                 android:checked="@{data.isExistingUserChecked}"
  107.                 android:id="@+id/returningUserRb"/>

  108.             <RadioButton
  109.                 android:layout_width="wrap_content"
  110.                 android:layout_height="wrap_content"
  111.                 android:text="New user"
  112.                 android:id="@+id/newUserRb"
  113.                 />

  114.         </RadioGroup>

  115.         <LinearLayout
  116.             android:orientation="horizontal"
  117.             android:layout_width="match_parent"
  118.             android:layout_height="wrap_content"
  119.             android:id="@+id/username_block"
  120.             android:layout_below="@+id/existingOrNewUser">

  121.             <TextView
  122.                 android:layout_width="wrap_content"
  123.                 android:layout_height="wrap_content"
  124.                 android:textAppearance="?android:attr/textAppearanceMedium"
  125.                 android:text="Username:"
  126.                 android:id="@+id/textView"
  127.                 android:minWidth="100dp"/>

  128.             <EditText
  129.                 android:layout_width="wrap_content"
  130.                 android:layout_height="wrap_content"
  131.                 android:id="@+id/username"
  132.                 android:minWidth="200dp"/>
  133.         </LinearLayout>

  134.         <LinearLayout
  135.             android:orientation="horizontal"
  136.             android:layout_width="match_parent"
  137.             android:layout_height="wrap_content"
  138.             android:layout_alignParentStart="false"
  139.             android:id="@+id/password_block"
  140.             android:layout_below="@+id/username_block">

  141.             <TextView
  142.                 android:layout_width="wrap_content"
  143.                 android:layout_height="wrap_content"
  144.                 android:textAppearance="?android:attr/textAppearanceMedium"
  145.                 android:text="Password:"
  146.                 android:minWidth="100dp"/>

  147.             <EditText
  148.                 android:layout_width="wrap_content"
  149.                 android:layout_height="wrap_content"
  150.                 android:inputType="textPassword"
  151.                 android:ems="10"
  152.                 android:id="@+id/password"/>

  153.         </LinearLayout>

  154.         <LinearLayout
  155.             android:orientation="horizontal"
  156.             android:layout_width="match_parent"
  157.             android:layout_height="wrap_content"
  158.             android:layout_below="@+id/password_block"
  159.             android:id="@+id/email_block"
  160.             android:visibility="@{data.emailBlockVisibility}">

  161.             <TextView
  162.                 android:layout_width="wrap_content"
  163.                 android:layout_height="wrap_content"
  164.                 android:textAppearance="?android:attr/textAppearanceMedium"
  165.                 android:text="Email:"
  166.                 android:minWidth="100dp"/>

  167.             <EditText
  168.                 android:layout_width="wrap_content"
  169.                 android:layout_height="wrap_content"
  170.                 android:inputType="textEmailAddress"
  171.                 android:ems="10"
  172.                 android:id="@+id/email"/>
  173.         </LinearLayout>

  174.         <Button
  175.             android:layout_width="wrap_content"
  176.             android:layout_height="wrap_content"
  177.             android:text="@{data.loginOrCreateButtonText}"
  178.             android:id="@+id/loginOrCreateButton"
  179.             android:layout_below="@+id/email_block"
  180.             android:layout_centerHorizontal="true"/>
  181.     </RelativeLayout>
  182. </layout>
複製代碼



MVVM – VIEW – JAVA

  1. package com.nilzor.presenterexample;

  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.CompoundButton;
  8. import android.widget.Toast;

  9. import com.nilzor.presenterexample.databinding.FragmentMainBinding;

  10. public class MainActivityFragment extends Fragment {
  11.     private FragmentMainBinding mBinding;
  12.     private MainModel mViewModel;

  13.     public MainActivityFragment() {
  14.     }

  15.     @Override
  16.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  17.         View view = inflater.inflate(R.layout.fragment_main, container, false);
  18.         mBinding = FragmentMainBinding.bind(view);
  19.         mViewModel = new MainModel(this, getResources());
  20.         mBinding.setData(mViewModel);
  21.         attachButtonListener();
  22.         return view;
  23.     }

  24.     private void attachButtonListener() {
  25.         mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
  26.             @Override
  27.             public void onClick(View v) {
  28.                 mViewModel.logInClicked();
  29.             }
  30.         });
  31.     }

  32.     @Override
  33.     public void onViewCreated(View view, Bundle savedInstanceState) {
  34.         ensureModelDataIsLodaded();
  35.     }

  36.     private void ensureModelDataIsLodaded() {
  37.         if (!mViewModel.isLoaded()) {
  38.             mViewModel.loadAsync();
  39.         }
  40.     }

  41.     public void showShortToast(String text) {
  42.         Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
  43.     }
  44. }
複製代碼


MVVM – VIEWMODEL

  1. package com.nilzor.presenterexample;

  2. import android.content.res.Resources;
  3. import android.databinding.ObservableField;
  4. import android.os.AsyncTask;
  5. import android.view.View;

  6. import java.util.Random;

  7. public class MainModel {
  8.     public ObservableField numberOfUsersLoggedIn = new ObservableField();
  9.     public ObservableField isExistingUserChecked = new ObservableField();
  10.     public ObservableField emailBlockVisibility = new ObservableField();
  11.     public ObservableField loginOrCreateButtonText = new ObservableField();
  12.     private boolean mIsLoaded;
  13.     private MainActivityFragment mView;
  14.     private Resources mResources;

  15.     public MainModel(MainActivityFragment view, Resources resources) {
  16.         mView = view;
  17.         mResources = resources; // You might want to abstract this for testability
  18.         setInitialState();
  19.         updateDependentViews();
  20.         hookUpDependencies();
  21.     }
  22.     public boolean isLoaded() {
  23.         return mIsLoaded;
  24.     }

  25.     private void setInitialState() {
  26.         numberOfUsersLoggedIn.set("...");
  27.         isExistingUserChecked.set(true);
  28.     }

  29.     private void hookUpDependencies() {
  30.         isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
  31.             @Override
  32.             public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
  33.                 updateDependentViews();
  34.             }
  35.         });
  36.     }

  37.     public void updateDependentViews() {
  38.         if (isExistingUserChecked.get()) {
  39.             emailBlockVisibility.set(View.GONE);
  40.             loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
  41.         }
  42.         else {
  43.             emailBlockVisibility.set(View.VISIBLE);
  44.             loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
  45.         }
  46.     }

  47.     public void loadAsync() {
  48.         new AsyncTask() {
  49.             @Override
  50.             protected Void doInBackground(Void... params) {
  51.                 // Simulating some asynchronous task fetching data from a remote server
  52.                 try {Thread.sleep(2000);} catch (Exception ex) {};
  53.                 numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
  54.                 mIsLoaded = true;
  55.                 return null;
  56.             }
  57.         }.execute((Void) null);
  58.     }

  59.     public void logInClicked() {
  60.         // Illustrating the need for calling back to the view though testable interfaces.
  61.         if (isExistingUserChecked.get()) {
  62.             mView.showShortToast("Invalid username or password");
  63.         }
  64.         else {
  65.             mView.showShortToast("Please enter a valid email address");
  66.         }
  67.     }
  68. }
複製代碼

原文鏈接:http://www.jianshu.com/p/4e3220a580f6
發表評論

最新評論

引用 gmvkg 2016-12-7 07:37
大家有什么好看法,赶快说说












澳规saa认证 好品质电源

查看全部評論(1)



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2024-3-29 00:41 , Processed in 0.057297 second(s), 24 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

返回頂部