在本教程中,我们将讨论并在Android应用程序中实现Android MVVM架构模式。我们之前讨论过[Android MVP Pattern](/community/tutorials/android-mvp)。
我们为什么需要这些模式? 在单个活动或片段中添加所有内容会导致测试和重构代码时出现问题。因此,建议使用代码分离和干净的架构。
Android MVVM
MVVM代表Model 、** view** 、** ViewModel** 。
- 模型 :保存应用程序的数据。它不能直接与View对话。一般情况下,建议通过可观察对象将数据公开给ViewModel。
- 视图 :表示没有任何应用逻辑的应用程序的用户界面。它观察视图模型。
- ViewModel :作为Model和View之间的纽带。它负责转换模型中的数据。它向View提供数据流。它还使用挂钩或回调来更新视图。它会向Model索要数据。
- 视图模型取代了中间层中的演示者。
- 演示者持有对该视图的引用。但ViewModel并非如此。
- 演示者使用经典方式(触发方法)更新视图。
- ViewModel发送数据流。
- 演示者和视图是1对1的关系。
- View和ViewModel是一对多关系。
- ViewModel不知道View正在监听它。
在Android中实现MVVM有两种方式:
- 数据绑定
- RXJava
在本教程中,我们将仅使用数据绑定。数据绑定库是由Google引入的,目的是在XML布局中直接绑定数据。有关数据绑定的更多信息,请参阅this教程。我们将创建一个简单的登录页面示例应用程序,要求用户输入。我们将看到ViewModel如何通知View何时在不保留View引用的情况下显示Toast消息。
如何在没有引用的情况下通知某个类? 它可以通过三种不同的方式完成:
- 使用双向数据绑定
- 使用实时数据
- 使用RxJava
双向数据绑定
双向数据绑定是一种将对象绑定到XML布局的技术,以便对象和布局都可以彼此发送数据。在我们的例子中,ViewModel可以将数据发送到布局,还可以观察更改。为此,我们需要在XML中定义一个BindingAdapter
和自定义属性。绑定适配器将侦听属性属性中的更改。通过下面的示例,我们将了解有关双向数据绑定的更多信息。
Android MVVM示例项目结构
添加数据绑定库
将以下代码添加到应用程序的build.gradle文件中:
1android {
2
3 dataBinding {
4 enabled = true
5 }
6}
这将在您的应用程序中启用数据绑定。
添加依赖关系
在您的Build.gradle
文件中添加以下依赖项:
1implementation 'android.arch.lifecycle:extensions:1.1.0'
机型
该模型将保存用户的电子邮件和密码。下面的User.java类实现了这一点:
1package com.journaldev.androidmvvmbasics.model;
2
3public class User {
4 private String email;
5 private String password;
6
7 public User(String email, String password) {
8 this.email = email;
9 this.password = password;
10 }
11
12 public void setEmail(String email) {
13 this.email = email;
14 }
15
16 public String getEmail() {
17 return email;
18 }
19
20 public void setPassword(String password) {
21 this.password = password;
22 }
23
24 public String getPassword() {
25 return password;
26 }
27
28}
双向数据绑定允许我们绑定XML布局中的对象,以便对象可以向布局发送数据,反之亦然。双向数据绑定的参数是@={variable}
布局
Active_main.xml的代码如下所示:
1<?xml version="1.0" encoding="utf-8"?>
2<layout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:bind="https://schemas.android.com/tools">
4
5 <data>
6
7 <variable
8 name="viewModel"
9 type="com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel" />
10 </data>
11
12 <ScrollView
13 android:layout_width="match_parent"
14 android:layout_height="match_parent">
15
16 <LinearLayout
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"
19 android:layout_gravity="center"
20 android:layout_margin="8dp"
21 android:orientation="vertical">
22
23 <EditText
24 android:id="@+id/inEmail"
25 android:layout_width="match_parent"
26 android:layout_height="wrap_content"
27 android:hint="Email"
28 android:inputType="textEmailAddress"
29 android:padding="8dp"
30 android:text="@={viewModel.userEmail}" />
31
32 <EditText
33 android:id="@+id/inPassword"
34 android:layout_width="match_parent"
35 android:layout_height="wrap_content"
36 android:hint="Password"
37 android:inputType="textPassword"
38 android:padding="8dp"
39 android:text="@={viewModel.userPassword}" />
40
41 <Button
42 android:layout_width="match_parent"
43 android:layout_height="wrap_content"
44 android:layout_marginTop="8dp"
45 android:onClick="@{()-> viewModel.onLoginClicked()}"
46 android:text="LOGIN"
47 bind:toastMessage="@{viewModel.toastMessage}" />
48
49 </LinearLayout>
50
51 </ScrollView>
52
53</layout>
数据绑定要求我们在顶部设置布局标记。在这里,我们的ViewModel将数据绑定到View。()->viewModel.onLoginClicked()
调用我们的ViewModel中定义的Button Click监听器lambda。EditText更新模型中的值(通过查看模型)。bind:toastMessage=
@{viewModel.toastMessage}``是我们为双向数据绑定创建的自定义属性。根据视图模型中toastMessage的更改,BindingAdapter将在视图中触发。
ViewModel
LoginViewModel.java的代码如下:
1package com.journaldev.androidmvvmbasics.viewmodels;
2
3import android.databinding.BaseObservable;
4import android.databinding.Bindable;
5import android.text.TextUtils;
6import android.util.Patterns;
7
8import com.android.databinding.library.baseAdapters.BR;
9import com.journaldev.androidmvvmbasics.model.User;
10
11public class LoginViewModel extends BaseObservable {
12 private User user;
13
14 private String successMessage = "Login was successful";
15 private String errorMessage = "Email or Password not valid";
16
17 @Bindable
18 private String toastMessage = null;
19
20 public String getToastMessage() {
21 return toastMessage;
22 }
23
24 private void setToastMessage(String toastMessage) {
25
26 this.toastMessage = toastMessage;
27 notifyPropertyChanged(BR.toastMessage);
28 }
29
30 public void setUserEmail(String email) {
31 user.setEmail(email);
32 notifyPropertyChanged(BR.userEmail);
33 }
34
35 @Bindable
36 public String getUserEmail() {
37 return user.getEmail();
38 }
39
40 @Bindable
41 public String getUserPassword() {
42 return user.getPassword();
43 }
44
45 public void setUserPassword(String password) {
46 user.setPassword(password);
47 notifyPropertyChanged(BR.userPassword);
48 }
49
50 public LoginViewModel() {
51 user = new User("","");
52 }
53
54 public void onLoginClicked() {
55 if (isInputDataValid())
56 setToastMessage(successMessage);
57 else
58 setToastMessage(errorMessage);
59 }
60
61 public boolean isInputDataValid() {
62 return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches() && getUserPassword().length() > 5;
63 }
64}
布局中调用的方法在上面的代码中使用相同的签名实现。如果与该方法对应的XML不存在,则需要将属性更改为app:
。上面的类还可以扩展ViewModel。但我们需要BaseObservable,因为它将数据转换为流,并在toastMessage
属性更改时发出通知。我们需要为XML中定义的toastMessage定制属性定义getter和setter。在setter内部,我们通知观察者(它将是我们应用程序中的View)数据已更改。View(我们的活动)可以定义适当的操作。
BR类是在重新生成项目时从数据绑定中自动生成的
MainActivity.java
类的代码如下:
1package com.journaldev.androidmvvmbasics.views;
2
3import android.databinding.BindingAdapter;
4import android.databinding.DataBindingUtil;
5import android.support.v7.app.AppCompatActivity;
6import android.os.Bundle;
7import android.view.View;
8import android.widget.Toast;
9
10import com.journaldev.androidmvvmbasics.R;
11import com.journaldev.androidmvvmbasics.databinding.ActivityMainBinding;
12import com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel;
13
14public class MainActivity extends AppCompatActivity {
15
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
20 activityMainBinding.setViewModel(new LoginViewModel());
21 activityMainBinding.executePendingBindings();
22
23 }
24
25 @BindingAdapter({"toastMessage"})
26 public static void runMe(View view, String message) {
27 if (message != null)
28 Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
29 }
30}
通过DataBinding,可以从布局中自动生成ActivityMainBinding
类。每当Button上定义的toastMessage属性发生变化时,就会触发@BindingAdapter
方法。它必须使用与在XML和ViewModel中定义的属性相同的属性。因此,在上面的应用程序中,ViewModel通过监听View中的更改来更新Model。此外,Model还可以使用NotifyPropertyChanged
通过ViewModel更新视图。上述应用程序的实际输出如下:这是关于Android MVVM使用数据绑定的教程的结束语。您可以从下面给出的链接下载该项目。