在這個教程中,我們將討論並實現 Android MVVM 架構模式在我們的 Android 應用程序中。我們之前已經討論過 Android MVP 模式。
我們為什麼需要這些模式?將所有內容都添加到單個 Activity 或 Fragment 中會導致測試和重構代碼時出現問題。因此,建議使用代碼分離和清晰的架構。
Android MVVM
MVVM 表示 Model、View、ViewModel。
- Model:這保存了應用程序的數據。它不能直接與 View 交談。通常建議通過 Observables 將數據暴露給 ViewModel。
- View:它代表了應用程序的 UI,不包含任何應用程序邏輯。它觀察 ViewModel。
- ViewModel:它充當 Model 和 View 之間的鏈接。負責轉換來自 Model 的數據。它向 View 提供數據流。它還使用鉤子或回調來更新 View。它將從 Model 請求數據。
- 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 通過 View Model 更新 Model 中的值。 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();
}
}
由於 DataBinding,ActivityMainBinding
類是從佈局自動生成的。每當在按鈕上定義的 toastMessage 屬性更改時,@BindingAdapter
方法就會觸發。它必須使用與 XML 和 ViewModel 中定義的相同屬性。因此,在上述應用中,ViewModel 通過監聽視圖中的變化來更新模型。此外,模型可以通過 ViewModel 使用 notifyPropertyChanged
更新視圖。上述應用的輸出如下所示:這結束了關於 Android MVVM 使用 DataBinding 的教程。您可以從下面給定的鏈接下載該項目。
Source:
https://www.digitalocean.com/community/tutorials/android-mvvm-design-pattern