Android MVVM设计模式
在本教程中,我们将在Android应用程序中讨论和实现Android MVVM体系结构模式。
我们之前已经讨论了Android MVP模式。
为什么我们需要这些模式?在"单个活动"或者"片段"中添加所有内容会导致测试和重构代码时出现问题。
因此,建议使用代码分离和干净的体系结构。
Android MVVM
MVVM代表模型,视图,视图模型。
模型:保存应用程序的数据。
它不能直接与视图对话。
通常,建议通过Observables将数据公开给ViewModel。视图:它表示没有任何应用程序逻辑的应用程序的UI。
它观察ViewModel。ViewModel:它充当模型和视图之间的链接。
它负责转换模型中的数据。
它向视图提供数据流。
它还使用挂钩或者回调来更新视图。
它将要求模型提供数据。
以下流程说明了核心MVVM模式。
这与MVP有何不同?
ViewModel替换中间层中的Presenter。
演示者持有对视图的引用。
ViewModel没有。演示者使用经典方式(触发方法)更新视图。
ViewModel发送数据流。
演示者和视图处于一对一关系。
View和ViewModel是一对多的关系。
ViewModel不知道View正在监听它。
有两种在Android中实现MVVM的方法:
- 数据绑定
- 接收Java
在本教程中,我们将仅使用数据绑定。
Google引入了数据绑定库,以便直接在xml布局中绑定数据。
有关数据绑定的更多信息,请参阅本教程。
我们将创建一个简单的登录页面示例应用程序,要求用户输入。
我们将看到ViewModel如何在不显示View引用的情况下何时显示Toast消息时通知View。
在没有参考的情况下如何通知某个类?
可以通过三种不同的方式完成此操作:
- 使用双向数据绑定
- 使用实时数据
- 使用RxJava
双向数据绑定
双向数据绑定是一种将对象绑定到XML布局的技术,这样对象和布局都可以相互发送数据。
在我们的例子中,ViewModel可以将数据发送到布局并观察更改。
为此,我们需要一个XML中定义的BindingAdapter和自定义属性。
绑定适配器将侦听attribute属性中的更改。
通过下面的示例,我们将详细了解双向数据绑定。
添加数据绑定库
将以下代码添加到您应用的build.gradle文件中:
android { dataBinding { enabled = true } }
这将在您的应用程序中启用数据绑定。
添加依赖项
在您的build.gradle文件中添加以下依赖项:
implementation 'android.arch.lifecycle:extensions:1.1.0'
模型
模型将保留用户的电子邮件和密码。
以下User.java类可以实现此目的:
package com.theitroad.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}
布局
下面给出了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.theitroad.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将数据绑定到View。()-> viewModel.onLoginClicked()
调用在我们的ViewModel中定义的Button点击监听器lambda。
EditText更新模型中的值(通过View Model)。
Bind:toastMessage =" @ {viewModel.toastMessage}""是我们为双向数据绑定创建的自定义属性。
根据ViewModel中toastMessage中的更改,将在View中触发BindingAdapter。
视图模型
下面给出了LoginViewModel.java的代码:
package com.theitroad.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.theitroad.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。
在设置器内部,我们通知观察者(在我们的应用程序中为View)数据已更改。
View(我们的活动)可以定义适当的操作。
重建项目时,BR类是根据数据绑定自动生成的
下面给出了MainActivity.java类的代码:
package com.theitroad.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.theitroad.androidmvvmbasics.R; import com.theitroad.androidmvvmbasics.databinding.ActivityMainBinding; import com.theitroad.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(); } }
多亏了DataBinding,ActivityMainBinding
类是从布局自动生成的。
每当在Button上定义的toastMessage属性更改时,都会触发@BindingAdapter方法。
它必须使用与XML和ViewModel中定义的属性相同的属性。
因此,在上述应用程序中,ViewModel通过侦听View中的更改来更新Model。
而且,模型可以使用notifyPropertyChanged
通过ViewModel更新视图。