在本教程中,我们将讨论并在我们的Android应用中实现Android MVVM架构模式。我们之前已经讨论过Android MVP模式。
我们为什么需要这些模式?在单个活动或片段中添加所有内容会导致在测试和重构代码时出现问题。因此,建议使用代码分离和清晰架构。
Android MVVM
MVVM代表模型,视图,视图模型。
- 模型:它保存应用程序的数据。它不能直接与视图通信。通常,建议通过可观察对象将数据公开给ViewModel。
- 视图:它代表应用程序的UI,不包含任何应用程序逻辑。它观察ViewModel。
- ViewModel:它充当模型和视图之间的链接。它负责从模型转换数据。它为视图提供数据流。它还使用挂钩或回调来更新视图。它将向模型请求数据。
- ViewModel取代了中间层的Presenter。
- Presenter持有对View的引用。ViewModel不持有。
- Presenter使用传统方法更新View(触发方法)。
- ViewModel发送数据流。
- Presenter和View之间是一对一的关系。
- View和ViewModel之间是一对多的关系。
- ViewModel不知道View正在监听它。
在Android中实现MVVM有两种方法:
- 数据绑定
- RXJava
在本教程中,我们将仅使用数据绑定。数据绑定库由Google引入,以便直接在xml布局中绑定数据。有关数据绑定的更多信息,请参阅此教程。我们将创建一个简单的登录页面示例应用程序,询问用户输入。我们将看到当ViewModel通知View何时显示Toast消息时,而不保留对View的引用。
如何在没有引用的情况下通知某个类? 可以通过三种不同的方式实现:
- 使用双向数据绑定
- 使用LiveData
- 使用RxJava
双向数据绑定
双向数据绑定是一种将对象绑定到XML布局的技术,以便对象和布局都可以相互发送数据。在我们的情况下,ViewModel可以向布局发送数据,并观察更改。为此,我们需要一个BindingAdapter
和在XML中定义的自定义属性。绑定适配器会监听属性属性的更改。我们将通过下面的示例了解更多关于双向数据绑定的内容。
Android MVVM示例项目结构
添加数据绑定库
将以下代码添加到您应用的build.gradle文件中:
android {
dataBinding {
enabled = true
}
}
这样可以在您的应用程序中启用数据绑定。
添加依赖项
在你的build.gradle
文件中添加以下依赖项:
implementation 'android.arch.lifecycle:extensions:1.1.0'
Model
Model将保存用户的电子邮件和密码。以下User.java类实现了这一点:
package com.journaldev.androidmvvmbasics.model;
public class User {
private String email;
private String password;
public User(String email, String password) {
this.email = email;
this.password = password;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
双向数据绑定允许我们将对象绑定到XML布局,以便对象可以向布局发送数据,反之亦然。双向数据绑定的语法是@={variable}
Layout
activity_main.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:bind="https://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:orientation="vertical">
<EditText
android:id="@+id/inEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"
android:padding="8dp"
android:text="@={viewModel.userEmail}" />
<EditText
android:id="@+id/inPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:padding="8dp"
android:text="@={viewModel.userPassword}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="@{()-> viewModel.onLoginClicked()}"
android:text="LOGIN"
bind:toastMessage="@{viewModel.toastMessage}" />
</LinearLayout>
</ScrollView>
</layout>
数据绑定要求我们在顶部设置布局标签。在这里,我们的ViewModel将数据绑定到视图。()-> viewModel.onLoginClicked()
调用了在我们的ViewModel中定义的按钮点击监听器lambda表达式。EditText更新Model中的值(通过ViewModel)。bind:toastMessage="@{viewModel.toastMessage}"
是我们为双向数据绑定创建的自定义属性。根据ViewModel中toastMessage的更改,BindingAdapter将在View中触发。
ViewModel
LoginViewModel.java的代码如下:
package com.journaldev.androidmvvmbasics.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.text.TextUtils;
import android.util.Patterns;
import com.android.databinding.library.baseAdapters.BR;
import com.journaldev.androidmvvmbasics.model.User;
public class LoginViewModel extends BaseObservable {
private User user;
private String successMessage = "Login was successful";
private String errorMessage = "Email or Password not valid";
@Bindable
private String toastMessage = null;
public String getToastMessage() {
return toastMessage;
}
private void setToastMessage(String toastMessage) {
this.toastMessage = toastMessage;
notifyPropertyChanged(BR.toastMessage);
}
public void setUserEmail(String email) {
user.setEmail(email);
notifyPropertyChanged(BR.userEmail);
}
@Bindable
public String getUserEmail() {
return user.getEmail();
}
@Bindable
public String getUserPassword() {
return user.getPassword();
}
public void setUserPassword(String password) {
user.setPassword(password);
notifyPropertyChanged(BR.userPassword);
}
public LoginViewModel() {
user = new User("","");
}
public void onLoginClicked() {
if (isInputDataValid())
setToastMessage(successMessage);
else
setToastMessage(errorMessage);
}
public boolean isInputDataValid() {
return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches() && getUserPassword().length() > 5;
}
}
在布局中调用的方法在上述代码中以相同的签名实现。如果方法的 XML 对应项不存在,我们需要将属性更改为 app:
。上述类也可以扩展 ViewModel。但是我们需要 BaseObservable,因为它将数据转换为流,并在 toastMessage
属性更改时通知。我们需要为在 XML 中定义的 toastMessage 自定义属性定义 getter 和 setter。在 setter 内部,我们通知观察者(这将是我们应用程序中的 View)数据已更改。View(我们的活动)可以定义适当的操作。
当您重新构建项目时,BR 类是从数据绑定中自动生成的
给出了 MainActivity.java
类的代码:
package com.journaldev.androidmvvmbasics.views;
import android.databinding.BindingAdapter;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.journaldev.androidmvvmbasics.R;
import com.journaldev.androidmvvmbasics.databinding.ActivityMainBinding;
import com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setViewModel(new LoginViewModel());
activityMainBinding.executePendingBindings();
}
@BindingAdapter({"toastMessage"})
public static void runMe(View view, String message) {
if (message != null)
Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
}
}
由于数据绑定,ActivityMainBinding
类是从布局自动生成的。每当在 Button 上定义的 toastMessage 属性更改时,@BindingAdapter
方法会触发。它必须使用与 XML 中定义的属性和 ViewModel 中定义的属性相同的属性。因此,在上述应用程序中,ViewModel 通过监听 View 中的更改来更新 Model。此外,Model 可以通过 ViewModel 使用 notifyPropertyChanged
更新视图。上述应用程序的操作输出如下:这就是关于使用数据绑定的 Android MVVM 的教程的结束。您可以从下面的链接下载项目。
Source:
https://www.digitalocean.com/community/tutorials/android-mvvm-design-pattern