开发人员始终喜欢为项目提供干净,结构化的代码。通过根据设计模式组织代码有助于软件的维护。通过了解android应用程序的所有关键逻辑部分,可以轻松添加和删除应用程序功能。此外,设计模式还确保所有代码都在单元测试中涵盖,而不会受到其他类的干扰。模型—视图— ViewModel(MVVM)是业界公认的软件体系结构模式,它克服了MVP和MVC设计模式的所有缺点。 MVVM建议将数据表示逻辑(视图或UI)与应用程序的核心业务逻辑部分分开。
MVVM的单独代码层是:
- 模型:该层负责数据源的抽象。 Model和ViewModel一起工作以获取和保存数据。
- 视图:该层的目的是向ViewModel通知用户的操作。该层遵守ViewModel,不包含任何类型的应用程序逻辑。
- ViewModel:它公开了与View相关的那些数据流。而且,它作为模型与视图之间的链接而充当服务器。
MVVM模式与MVP(模型—视图—演示者)设计模式有一些相似之处,因为演示者角色是由ViewModel扮演的。但是,MVVM已通过以下方式解决了MVP模式的缺点:
- ViewModel不保存对View的任何引用。
- View和ViewModel之间存在多对一的关系。
- 没有触发方法来更新视图。
在项目中实现MVVM的方法
有两种方法可以在Android项目中实现MVVM设计模式:
- 使用Google发布的DataBinding库
- 使用RxJava之类的任何工具进行DataBinding。
数据绑定:
Google发布了适用于Android的数据绑定库,该库允许开发人员将XML布局中的UI组件与应用程序的数据存储库绑定。这有助于最大程度地减少与View绑定的核心应用程序逻辑的代码。此外,完成了双向数据绑定以将对象绑定到XML布局,以便对象和布局都可以相互发送数据。这一点可以通过本教程的示例直观地看到。
Syntax for the two way data binding is @={variable}
MVVM体系结构模式示例
这是一个单一活动User-Login android应用程序的示例,用于显示MVVM架构模式在项目上的实现。该应用程序将要求用户输入电子邮件ID和密码。根据收到的输入,ViewModel通知View显示为Toast消息。 ViewModel将没有对View的引用。
To enable DataBinding in the android application, following codes needs to be added in the app’s build.gradle(build.gradle (:app)) file:
Enable DataBinding:
android {
dataBinding {
enabled = true
}
}
Add lifecycle dependency:
implementation ‘android.arch.lifecycle:extensions:1.1.1’
以下是带有MVVM模式的User-Login android应用程序的完整分步实施。
分步实施
Note: Following steps are performed on Android Studio version 4.0
步骤1:创建一个新项目
- 单击文件,然后单击新建=>新建项目。
- 选择清空活动
- 选择语言作为Java/科特林
- 根据需要选择最小的SDK。
步骤2:修改String.xml文件
此文件中列出了活动中使用的所有字符串。
XML
GfG | MVVM Architecture
MVVM Architecture Pattern
Enter your Email ID
Enter your password
Login
Java
import androidx.annotation.Nullable;
public class Model {
@Nullable
String email,password;
// constructor to initialize
// the variables
public Model(String email, String password){
this.email = email;
this.password = password;
}
// getter and setter methods
// for email variable
@Nullable
public String getEmail() {
return email;
}
public void setEmail(@Nullable String email) {
this.email = email;
}
// getter and setter methods
// for password variable
@Nullable
public String getPassword() {
return password;
}
public void setPassword(@Nullable String password) {
this.password = password;
}
}
XML
Java
import android.text.TextUtils;
import android.util.Patterns;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class AppViewModel extends BaseObservable {
// creating object of Model class
private Model model;
// string variables for
// toast messages
private String successMessage = "Login successful";
private String errorMessage = "Email or Password is not valid";
@Bindable
// string variable for
// toast message
private String toastMessage = null;
// getter and setter methods
// for toast message
public String getToastMessage() {
return toastMessage;
}
private void setToastMessage(String toastMessage) {
this.toastMessage = toastMessage;
notifyPropertyChanged(BR.toastMessage);
}
// getter and setter methods
// for email varibale
@Bindable
public String getUserEmail() {
return model.getEmail();
}
public void setUserEmail(String email) {
model.setEmail(email);
notifyPropertyChanged(BR.userEmail);
}
// getter and setter methods
// for password variable
@Bindable
public String getUserPassword() {
return model.getPassword();
}
public void setUserPassword(String password) {
model.setPassword(password);
notifyPropertyChanged(BR.userPassword);
}
// constructor of ViewModel class
public AppViewModel() {
// instantiating object of
// model class
model = new Model("","");
}
// actions to be performed
// when user clicks
// the LOGIN button
public void onButtonClicked() {
if (isValid())
setToastMessage(successMessage);
else
setToastMessage(errorMessage);
}
// method to keep a check
// that variable fields must
// not be kept empty by user
public boolean isValid() {
return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches()
&& getUserPassword().length() > 5;
}
}
Java
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;
import com.example.mvvmarchitecture.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ViewModel updates the Model
// after observing changes in the View
// model will also update the view
// via the ViewModel
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setViewModel(new AppViewModel());
activityMainBinding.executePendingBindings();
}
// any change in toastMessage attribute
// defined on the Button with bind prefix
// invokes this method
@BindingAdapter({"toastMessage"})
public static void runMe( View view, String message) {
if (message != null)
Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
}
}
步骤3:建立Model类别
创建一个名为Model的新类,该类将保存用户输入的电子邮件ID和密码。下面是实现适当的Model类的代码。
Java
import androidx.annotation.Nullable;
public class Model {
@Nullable
String email,password;
// constructor to initialize
// the variables
public Model(String email, String password){
this.email = email;
this.password = password;
}
// getter and setter methods
// for email variable
@Nullable
public String getEmail() {
return email;
}
public void setEmail(@Nullable String email) {
this.email = email;
}
// getter and setter methods
// for password variable
@Nullable
public String getPassword() {
return password;
}
public void setPassword(@Nullable String password) {
this.password = password;
}
}
步骤4:使用activity_main.xml文件
打开activity_main.xml文件,并添加2 EditText以获取“电子邮件”和“密码”的输入。还需要一个登录按钮来验证用户的输入并显示适当的Toast消息。以下是用于设计适当活动布局的代码。
Note: For the proper functioning of Data Binding Library, it is required to set the layout tag at the top. The constraint layout tag of XML will not work in this case.
XML格式
步骤5:创建ViewModel类
此类将包含在应用程序布局中需要调用的所有方法。 ViewModel类将扩展BaseObservable,因为它会将数据转换为流,并在Toast消息属性更改时通知View 。
Java
import android.text.TextUtils;
import android.util.Patterns;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class AppViewModel extends BaseObservable {
// creating object of Model class
private Model model;
// string variables for
// toast messages
private String successMessage = "Login successful";
private String errorMessage = "Email or Password is not valid";
@Bindable
// string variable for
// toast message
private String toastMessage = null;
// getter and setter methods
// for toast message
public String getToastMessage() {
return toastMessage;
}
private void setToastMessage(String toastMessage) {
this.toastMessage = toastMessage;
notifyPropertyChanged(BR.toastMessage);
}
// getter and setter methods
// for email varibale
@Bindable
public String getUserEmail() {
return model.getEmail();
}
public void setUserEmail(String email) {
model.setEmail(email);
notifyPropertyChanged(BR.userEmail);
}
// getter and setter methods
// for password variable
@Bindable
public String getUserPassword() {
return model.getPassword();
}
public void setUserPassword(String password) {
model.setPassword(password);
notifyPropertyChanged(BR.userPassword);
}
// constructor of ViewModel class
public AppViewModel() {
// instantiating object of
// model class
model = new Model("","");
}
// actions to be performed
// when user clicks
// the LOGIN button
public void onButtonClicked() {
if (isValid())
setToastMessage(successMessage);
else
setToastMessage(errorMessage);
}
// method to keep a check
// that variable fields must
// not be kept empty by user
public boolean isValid() {
return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches()
&& getUserPassword().length() > 5;
}
}
步骤6:在MainActivity文件中定义View的功能
View类负责更新应用程序的UI。根据ViewModel提供的Toast消息中的更改,绑定适配器将触发View层。 Toast消息的设置者将通知观察者(View)有关数据的更改。之后,View将采取适当的措施。
Java
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;
import com.example.mvvmarchitecture.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ViewModel updates the Model
// after observing changes in the View
// model will also update the view
// via the ViewModel
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setViewModel(new AppViewModel());
activityMainBinding.executePendingBindings();
}
// any change in toastMessage attribute
// defined on the Button with bind prefix
// invokes this method
@BindingAdapter({"toastMessage"})
public static void runMe( View view, String message) {
if (message != null)
Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
}
}
输出
MVVM体系结构的优势
- 增强代码的可重用性。
- 所有模块都是独立的,从而提高了每个层的可测试性。
- 使项目文件可维护且易于更改。
MVVM体系结构的缺点
- 这种设计模式不适用于小型项目。
- 如果数据绑定逻辑太复杂,则应用程序调试会更加困难。