תבנית עיצוב Android MVVM

במדריך זה, נדבר ונממש את תבנית הארכיטקטורה ה-MVVM באפליקציית ה-Android שלנו. דיברנו קודם על תבנית ה-MVP של Android.

למה אנחנו זקוקים לתבניות אלו? להוסיף הכל בפעולה או פרגמנט יכול להוביל לבעיות בבדיקה ובתיקוני קוד. לכן, מומלץ להשתמש בהפרדה של הקוד ובארכיטקטורה נקייה.

Android MVVM

MVVM עומדת על המילים מודל, תצוגה, מודל תצוגה.

  • מודל: מחזיק את נתוני היישום. הוא לא יכול לדבר ישירות עם התצוגה. בדרך כלל, מומלץ לחשוף את הנתונים למודל התצוגה דרך Observables.
  • תצוגה: מייצג את ממשק המשתמש של היישום ללא כל לוגיקת יישום. היא מתבוננת במודל התצוגה.
  • מודל תצוגה: משמש כקשר בין המודל והתצוגה. הוא אחראי להמרת הנתונים מהמודל. הוא מספק זרמי נתונים לתצוגה. הוא גם משתמש בגיגים או ב-callbacks כדי לעדכן את התצוגה. הוא יבקש את הנתונים מהמודל.

הזרם הבא ממחיש את תבנית ה־MVVM היסודית. במה זה שונה מ־MVP?

  • ViewModel מחליף את ה־Presenter בשכבת האמצע.
  • ה־Presenter מחזיק הפניות ל־View. ה־ViewModel אינו.
  • ה־Presenter מעדכן את ה־View באמצעות הדרך הקלאסית (הפעלת שיטות).
  • ה־ViewModel שולח זרמי נתונים.
  • ה־Presenter וה־View נמצאים ביחס 1 ל־1.
  • ה־View וה־ViewModel נמצאים ביחס 1 ל־רבים.
  • ה־ViewModel לא יודעת שה־View מקשיב לה.

ישנם שני דרכים ליישום של MVVM ב־Android:

  • קישור נתונים
  • ‏RXJava

במדריך זה, נשתמש רק ב־Data Binding. ספריית Data Binding נוכנה על ידי Google כדי לקשור נתונים ישירות בפריסת ה־xml. למידע נוסף על Data Binding, ראו כאן את המדריך. ניצור דוגמת יישום דף התחברות פשוט ששואל את המשתמש להזין נתונים. נראה כיצד ה־ViewModel מודיע ל־View מתי להציג הודעת Toast מבלי לשמור הפנייה ל־View.

כיצד ניתן להודיע למחלקה מסוימת מבלי להחזיק הפנייה אליה? זה ניתן לעשות בשלושה דרכים שונות:

  • בשימוש ב־Two Way Data Binding
  • בשימוש ב־Live Data
  • בשימוש ב־RxJava

קישור דו-כיווני לנתונים

קישור דו-כיווני לנתונים הוא טכניקה לקישור אובייקטים שלך לפריסות XML שלך כך שהאובייקט והפריסה יכולים שניהם לשלוח נתונים זה לזה. במקרה שלנו, ViewModel יכול לשלוח נתונים לפריסה וגם לצפות בשינויים. עבור זה, אנו זקוקים ל-BindingAdapter ולמאפיין מותאם אישית שמוגדר ב-XML. מתאם הקשר יקשיב לשינויים במאפיין המאפיין. נלמד עוד על קישור דו-כיווני לנתונים דרך הדוגמה שלנו למטה.

מבנה פרויקט דוגמה ל-Android MVVM

הוספת ספריית הקישור לנתונים

הוספת הקוד הבא לקובץ build.gradle של האפליקציה שלך:

android {

    dataBinding {
        enabled = true
    }
}

זה מאפשר קישור לנתונים באפליקציה שלך.

הוספת התלות

הוסף את התלות הבאה בקובץ ה-build.gradle שלך:

implementation 'android.arch.lifecycle:extensions:1.1.0'

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 שלנו. ה-EditText מעדכן את הערכים במודל (דרך ViewModel). bind:toastMessage="@{viewModel.toastMessage}" הוא מאפיין מותאם שיצרנו עבור קישור נתונים דו-כיווני. בהתבסס על שינויים ב-toastMessage ב-ViewModel, מנגנון ה-bind היה מפעיל במבנה ה-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 משתנה. עלינו להגדיר את ה-getter וה-setter עבור התכונה המותאמת toastMessage המוגדרת ב-XML. בתוך ה-setter, אנו מודיעים לצופה (שיהיה התצוגה באפליקציה שלנו) כי הנתונים השתנו. התצוגה (הפעילות שלנו) יכולה להגדיר את הפעולה המתאימה.

BR class נוצר באופן אוטומטי מ-Data Binding כאשר אתה משחזר את הפרוייקט

קוד למחלקת 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 נוצרת באופן אוטומטי מהפריסה. שיטת @BindingAdapter מתפעלת בכל פעם שתכונת toastMessage מוגדרת על הכפתור משתנה. עליה להשתמש באותו מאפיין שנגדיר ב-XML וב-ViewModel. כך שבאפליקציה שלנו למעלה, ViewModel מעדכן את המודל על ידי האזנה לשינויים בתצוגה. בנוסף, המודל יכול לעדכן את התצוגה דרך ה-ViewModel באמצעות השימוש ב-notifyPropertyChanged. פלט האפליקציה לעיל בפעולה ניתן למטה:זה מסיים את המדריך הזה על Android MVVM באמצעות DataBinding. ניתן להוריד את הפרוייקט מהקישור המצורף למטה.

AndroidMVVMBasics

קישור לפרויקט ב-Github

Source:
https://www.digitalocean.com/community/tutorials/android-mvvm-design-pattern