معرفی معماری mvvm اندروید

معرفی کامل معماری MVVM در اندروید + روش پیاده سازی

برنامه نویس ها همیشه ترجیح میدهند کدهای پروژه دارای ساختار و نظم مناسبی باشند. حتما شما هم پروژه های قدیمی خودتان را باز کردید و با دیدن کدها از خودتان پرسیدید که موقع نوشتن این کدها چه فکری میکردم؟ اصلا این کد ها را چه کسی نوشته است؟ این سوالات به دلیل نبود ساختار مشخص و معماری مناسب در زمان کدنویسی اپلیکیشن به وجود می آیند. به همین خاطر در این نوشته از برنامه چی میخواهیم یکی از معروفترین و قدرتمندترین معماری های اندروید به نام معماری MVVM را کاملا بررسی کنیم. با ما همراه باشید.

معماری mvvm اندروید

در مورد دیزاین پترن های اندروید در مقاله های جداگانه بصورت تخصصی صحبت کردیم. (برای آشنایی بهتر با همه این ساختار ها در اندروید این نوشته را مطالعه کنید: دیزاین پترن های معروف اندروید). این مقاله درباره معرفی معماری MVVM، لایه های این معماری، دیاگرام اجرا و نحوه پیاده سازی آن در پروژه بحث میکند. لیست تیتر ها را میتوانید در باکس زیر مشاهده کنید.


معرفی معماری MVVM اندروید

معرفی معماری MVVM

معماری MVVM یکی از معماری های جدید و شناخته شده اندروید است که از معماری MVP (ساخته شده توسط Martin Fowler در دهه 1990) مشتق شده است. معماری MVVM دارای سه لایه به نام های Model، View و ViewModel میباشد که میتواند منطق بیزینس و Presentation را از رابط کاربری جدا کند. جداسازی UI و Business Logic تاثیر بسیار زیادی در تست پذیری اپلیکیشن و نگهداری و توسعه آن در آینده دارند.

معماری MVVM یک معماری شناخته شده برای صنعت تولید نرم افزار است که معایب و کاستی های معماری MVP و معماری MVC درون آن وجود ندارند.


اجزای معماری MVVM اندروید

معماری MVVM مانند بقیه معماری های موجود برای سیستم عامل اندروید، دارای لایه هایی است که عملیات دسته بندی وظایف و کدهای اپلیکیشن ما را انجام میدهند. در این بخش میخواهیم با لایه های این معماری آشنا بشویم.


لایه Model

لایه Model معماری mvvm

لایه Model مسئول فراهم کردن داده ها و منطق بیزینسی اپلیکیشن ما میباشد. همه داده های اپلیکیشن که از منابع مختلف مانند دیتابیس، سرور، فایل، یا حافظه های در دسترس تامین میشوند، در این لایه قرار میگیرند. این لایه برای کاربر قابل دیدن نیست و کلاس های مربوط به ذخیره سازی و دسترسی به داده ها را درون خودش جا میدهد. لایه های Model و ViewModel در این معماری برای ذخیره و دریافت داده ها همکاری زیادی دارند.

داده ها میتوانند از منابع مختلفی به دست بیایند، مانند:

  • REST API
  • {لینک API}
  • دیتابیس Realm
  • دیتابیس SQLite
  • مدیریت Broadcast
  • Shared Preferences
  • Firebase
  • و غیره.

لایه View

لایه view معماری mvvm

لایه View مسئول نمایش همه اجزای رابط کاربری (اکتیویتی، فرگمنت و کدهای XML) در اپلیکیشن ما میباشد. وظیفه لایه View این است که همه اکشن های کاربر را به لایه ViewModel اطلاع بدهد، اما بصورت مستقیم هیچ پاسخی را از آن دریافت نمی کند. به جای دریافت پاسخ مستقیم، لایه View باید Observable های موجود در لایه ViewModel را Observe (یا Subscribe) کند. ارتباط این دو لایه از این طریق برقرار میشود. در معماری MVVM، لایه View دارای ارجاع مستقیم به ViewModel است اما ViewModel هیچ ارجاع مستقیمی به View ندارد.

دقت داشته باشید که این لایه نباید شامل هیچکدام از منطق های بیزنسی اپلیکیشن باشد، ولی ما میتوانیم منطق مربوط به رابط کاربری را در این لایه پیاده سازی کنیم.

لایه View وظیفه دارد موارد زیر را مدیریت کند:

  • منو ها
  • مجوز ها (Permissions)
  • Event Listeners
  • نمایش دیالوگ، پیغام Toast و Snackbar
  • کار کردن با کلاس های View و Widget در اندروید
  • شروع کردن اکتیویتی ها
  • همه عملکرد هایی که به Context اندروید مربوط میشوند

لایه ViewModel

در حالت عادی، لایه ViewModel همه داده ها و دستورات مربوط به لایه View را درون خود نگهداری میکند تا بتواند تغییرات به وجود آمده را با استفاده از Event ها به اطلاع لایه View برساند. سپس وقتی که لایه View این اعلان ها را دریافت کرد، تصمیم میگیرد که چگونه باید آنها را نمایش بدهد.

بنابراین وظیفه این لایه دریافت اکشن های کاربر از لایه View، آپدیت کردن لایه Model و در نهایت انتشار جریان های داده مربوط به لایه View است که طبق آنها لایه View میتواند تصمیم بگیرد چه تغییراتی را باید روی رابط کاربری اعمال کند.

لایه viewmodel معماری mvvm

نکته: لایه ViewModel نباید هیچ ارجاع مستقیمی از View را درون خود نگهداری کند. یا به عبارت دیگر این لایه هیچ اطلاعی از اینکه با کدام View در حال کار کردن است، ندارد. با رعایت این نکته، بیشتر از یک View میتواند از یک لایه ViewModel استفاده کند.

دقت داشته باشید لایه ViewModel در حالت عادی با لایه ViewModel که همراه با AAC (Android Architecture Component) استفاده میشود تفاوت دارد. لایه ViewModel که با AAC به کار برده میشود، در حقیقت یک کلاس است که از چرخه عمر لایه View با خبر است و میتواند داده های آن را در تغییرات مختلف (مثلا چرخیدن صفحه و ریست شدن اکتیویتی) حفط کند.

در مورد Android Architecture Component و استفاده از آن همراه با معماری MVVM به زودی مقاله آموزشی کاملی در وبسایت قرار میدهیم. اما شما میتوانید این معماری را بدون AAC هم در اپلیکیشن خودتان پیاده سازی کنید. آموزش روش های مختلف پیاده سازی در ادامه همین مقاله وجود دارند.

وظایف زیر مربوط به لایه ViewModel هستند:

  • انتشار داده ها
  • انتشار وضعیت (پیشرفت، آفلاین، خالی، ارور و غیره)
  • مدیریت Visibility
  • اعتبار سنجی ورودی ها
  • اجرای فراخوانی های لایه Model
  • اجرای متد های لایه View

لایه ViewModel باید فقط از Application Context اطلاع داشته باشد. Application Context میتواند کارهای زیر را انجام بدهد:

  • شروع یک Service
  • Bind شدن به یک Service
  • ارسال Broadcast
  • ثبت نام یک Broadcast Receiver
  • بارگذاری Resource ها

Application Context کارهای زیر را نمیتواند انجام بدهد:

  • نمایش یک دیالوگ
  • شروع کردن یک اکتیویتی
  • Inflate کردن Layout

کامپوننت ها و ارتباط بین آنها

کامپوننت های معماری mvvm

همانطور که با هم بررسی کردیم، معماری MVVM دارای سه لایه به نام های Model، View و ViewModel میباشد. وقتی که اپلیکیشن ما با استفاده از این دیزاین پترن ساخته شده باشد، لایه های آن به خوبی از هم جدا شده اند و به خوبی کار میکنند.

لایه View در این معماری همه اکشن های کاربر را دریافت میکند و به لایه ViewModel اطلاع میدهد. سپس لایه ViewModel به لایه Model اطلاع میدهد که داده ها را ذخیره کند و داده های جدید را از آن دریافت میکند. سپس با استفاده از جریان های داده ای، این دیتا های جدید را برای نمایش داده شدن به اطلاع لایه View میرساند.

دقت داشته باشید که لایه ViewModel از جریان های داده (که توی این مقاله درباره اونها صحبت کردیم: برنامه نویسی Reactive اندروید) استفاده میکند. به همین دلیل دیگر نیازی به ساختن اینترفیس های زیاد مانند چیزی که در معماری MVP داشتیم، نیست. این جریان ها داده ای میتوانند همه تغییرات به وجود آمده در داده ها را به اطلاع Observer ها میرساند.


مزایا

  • بسیاری از تلاش ها و کارهای اضافی برای پیاده سازی معماری در اپلیکیشن اندروید، راحت تر شده اند.
  • توسعه دهنده امکان استفاده از کتابخانه های رسمی گوگل را دارد. این یعنی کامپوننت ها و المان های بسیار قدرتمند تری در اختیار برنامه نویس ها میباشد.
  • کدهای استفاده شده در این معماری ساده سازی شده اند.
  • جداسازی لایه های مختلف در معماری MVVM به خوبی انجام شده اند.

معایب

  • در پروژه های بسیار بزرگ ممکن است کلاس های ViewModel خط های بسیار زیادی داشته باشند که بررسی آنها برای برنامه نویس سخت میشود.
  • در تعداد کمی از موارد، شاید کدها در XML اجرا شوند که این مسئله ممکن است برنامه نویس ها را دچار سردرگمی کند.

پیاده سازی معماری MVVM

برای پیاده سازی معماری MVVM در اپلیکیشن های اندرویدی خودمان راه های مختلفی داریم. شما میتوانید با مطالعه دقیق روش های پیاده سازی معماری MVVM، روش مناسب برای خودتان را پیدا کنید. اما در این مقاله میخواهیم چند روش پر استفاده برای پیاده سازی این معماری را با هم بررسی کنیم.


روش اول: استفاده از LiveData و Architecture Components

برای پیاده سازی معماری MVVM با استفاده از LiveData و Android Architecture Components، میخواهیم یک پروژه ساده را در اندروید استودیو بسازیم. در این پروژه اطلاعات ایمیل و پسورد از کاربر گرفته شده، اعتبار سنجی میشود و یک پیغام Toast برای کاربر به نمایش در خواهد آمد. مراحل پیاده سازی این پروژه را بصورت گام به گام با هم طی میکنیم.


گام اول – ساخت پروژه

  1. اندروید استودیو را باز کنید.
  2. روی گزینه File کلیک کنید، از منوی New، گزینه New Project را بزنید.
  3. گزینه Empty Activity را انتخاب کنید.
  4. زبان را روی Java/Kotlin قرار بدهید.
  5. مقدار Minimum SDK را هرطور که میخواهید انتخاب کنید.
  6. کدهای زیر را در فایل build.gradle سطح app وارد کنید:
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

گام دوم – ساخت کلاس های لایه Model

در این مرحله یک کلاس به نام Model درست میکنیم که همه داده های ورودی کاربر در این کلاس ذخیره خواهند شد. متد های getter و setter را نیز برای متغیر های موجود در این کلاس تعریف میکنیم. کدهای زیر را مشاهده کنید.

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;
	}
}

گام سوم – ساخت کلاس های لایه ViewModel

برای ساخت لایه ViewModel، از کلاسی به نام LiveData استفاده میکنیم. بنابراین یک کلاس به نام MainViewModel میسازیم که کلاس ViewModel را extend میکند و تعدادی از آبجکت های LiveData درون آن قرار دارند. LiveData در حقیقت یک نگهدارنده اطلاعات است که کامپوننت های اپلیکیشن میتوانند آن را Observe کنند و هرموقع تغییری در داده های اتفاق بیفتد، همه کامپوننت هایی که Observe کرده باشند، با خبر خواهند شد. کد های زیر روش پیاده سازی آن را نشان میدهد:

import android.text.TextUtils;
import android.util.Patterns;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
    // string variables for
    // toast messages
    private String successMessage = "Login successful";
    private String errorMessage = "Email or Password is not valid";
    private String email = "";
    private String password = "";
    private MutableLiveData<String> toastMessage = new MutableLiveData<>();
    public void isValid() {
        if (!TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches()
                && password.length() > 5) {
            toastMessage.setValue(successMessage);
        } else {
            toastMessage.setValue(errorMessage);
        }
    }
    public LiveData<String> getToastMessage() {
        return toastMessage;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

گام چهارم – ساخت اکتیویتی (مربوط به لایه View)

در اپلیکیشن ما، اکتیویتی نقش لایه View را به عهده دارد. برای همین یک اکتیویتی خالی به پروژه اضافه میکنیم (اگر روش ساختن اکتیویتی رو بلد نیستین، حتما این مقاله رو مطالعه کنین: اکتیویتی ها در اندروید).


گام پنجم – ویرایش فایل activity_main.xml (مربوط به لایه View)

برای داشتن EditText های مختلف که داده های کاربر را دریافت میکنند، باید فایل activity_main.xml را ویرایش کنیم. کدهای زیر نشان میدهند چگونه باید این کار انجام شود.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	xmlns:bind="http://schemas.android.com/tools">
	<!-- Provided Linear layout for the activity components -->
	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:layout_gravity="center"
		android:layout_margin="8dp"
		android:background="#168BC34A"
		android:orientation="vertical">
		<!-- TextView for the heading of the activity -->
		<TextView
			android:id="@+id/textView"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@string/heading"
			android:textAlignment="center"
			android:textColor="@android:color/holo_green_dark"
			android:textSize="36sp"
			android:textStyle="bold" />
		<!-- EditText field for the Email -->
		<EditText
			android:id="@+id/inEmail"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="10dp"
			android:layout_marginTop="60dp"
			android:layout_marginEnd="10dp"
			android:layout_marginBottom="20dp"
			android:hint="@string/email_hint"
			android:inputType="textEmailAddress"
			android:padding="8dp"
			android:text="@={viewModel.userEmail}" />
		<!-- EditText field for the password -->
		<EditText
			android:id="@+id/inPassword"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="10dp"
			android:layout_marginEnd="10dp"
			android:hint="@string/password_hint"
			android:inputType="textPassword"
			android:padding="8dp"
			android:text="@={viewModel.userPassword}" />
		<!-- Login Button of the activity -->
		<Button
			android:id="@+id/loginBtn"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="20dp"
			android:layout_marginTop="60dp"
			android:layout_marginEnd="20dp"
			android:background="#4CAF50"
			android:fontFamily="@font/roboto"
			android:text="@string/button_text"
			android:textColor="@android:color/background_light"
			android:textSize="30sp"
			android:textStyle="bold" />
		<ImageView
			android:id="@+id/imageView"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginTop="135dp"
			app:srcCompat="@drawable/banner" />
	</LinearLayout>
</layout>

گام ششم – ویرایش فایل کلاس MainActivity (مربوط به لایه View)

بعد از ساختن اکتیویتی، باید کدهای مربوط به رابط کاربری و همچنین آبجکت کلاس ViewModel مورد نظر را درون آن قرار بدهیم. نمونه کدهای زیر را مشاهده کنید:

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    private MainViewModel mainViewModel;
    private EditText emailEt;
    private EditText passwordEt;
    private Button loginBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        mainViewModel.getToastMessage().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT);
            }
        });
        emailEt = findViewById(R.id.inEmail);
        passwordEt = findViewById(R.id.inPassword);
        loginBtn = findViewById(R.id.loginBtn);
        if (mainViewModel.getEmail() != null) {
            emailEt.setText(mainViewModel.getEmail());
        }
        if (mainViewModel.getPassword() != null) {
            passwordEt.setText(mainViewModel.getPassword());
        }
        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mainViewModel.setEmail(String.valueOf(emailEt.getText()));
                mainViewModel.setPassword(String.valueOf(passwordEt.getText()));
                mainViewModel.isValid();
            }
        });
    }
}

روش دوم: استفاده از Data Binding

در این قسمت یک مثال ساده از معماری MVVM که شامل یک اکتیویتی برای Login کردن کاربر است را با هم بررسی میکنیم تا با پیاده سازی معماری MVVM در پروژه ها آشنا شوید. این اپلیکیشن از کاربر درخواست میکند که ایمیل و پسورد را وارد کند. با توجه به ورودی های دریافت شده، لایه ViewModel به لایه View خبر میدهد که یک پیغام Toast نمایش بدهد. یادتان باشد لایه ViewModel هیچ ارجاعی به View نگهداری نمی کند.


گام اول – ساخت پروژه

  1. اندروید استودیو را باز کنید.
  2. روی گزینه File کلیک کنید، از منوی New، گزینه New Project را بزنید.
  3. گزینه Empty Activity را انتخاب کنید.
  4. زبان را روی Java/Kotlin قرار بدهید.
  5. مقدار Minimum SDK را هرطور که میخواهید انتخاب کنید.

گام دوم – فعال کردن Data Binding

برای فعال کردن Data Binding در اپلیکیشن اندروید، کدهای زیر را باید در فایل build.gradle سطح اپ (build.gradle(:app)) اضافه کنیم:

android {
   dataBinding {
       enabled = true
      }
}

قطعه کد زیر را هم در قسمت implementation ها اضافه میکنیم:

def life_versions = "1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:$life_versions"
annotationProcessor "android.arch.lifecycle:compiler:$life_versions"

حالا به سراغ پیاده سازی بقیه قسمت های اپلیکیشن میرویم.


گام سوم – ویرایش فایل String.xml

همه رشته متن هایی که در اپلیکیشن استفاده میکنید در این فایل قرار دارند.

<resources>
	<string name="app_name">GfG | MVVM Architecture</string>
	<string name="heading">MVVM Architecture Pattern</string>
	<string name="email_hint">Enter your Email ID</string>
	<string name="password_hint">Enter your password</string>
	<string name="button_text">Login</string>
</resources>

گام چهارم – ساخت کلاس لایه Model

یک کلاس به نام Model میسازیم که ایمیل و پسورد وارد شده توسط کاربر را نگهداری میکند. کدهای زیر نحوه صحیح ساخت این کلاس را در کدهای زیر مشاهده میکنید.

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;
	}
}

گام پنجم – کار با فایل activity_main.xml

فایل activity_main.xml را باز میکنیم و 2 عدد EditText برای وارد کردن ایمیل و پسورد به آن اضافه میکنیم. یک دکمه Login برای اعتبارسنجی ورودی ها و نشان دادن پیام Toast نیاز است. در باکس زیر میتوانید کدهای مربوط به این کار را ببینید.

نکته: برای اینکه کتابخانه Data Binding به درستی کار کند، نیاز داریم که تگ مربوط به آن را در بالای فایل تعریف کنیم. نکته دیگر این است که در صورت استفاده از این تگ، Constraint Layout دیگر قابل استفاده نخواهد بود.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	xmlns:bind="http://schemas.android.com/tools">
	<!-- binding object of ViewModel to the XML layout -->
	<data>
		<variable
			name="viewModel"
			type="com.example.mvvmarchitecture.AppViewModel" />
	</data>
	<!-- Provided Linear layout for the activity components -->
	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:layout_gravity="center"
		android:layout_margin="8dp"
		android:background="#168BC34A"
		android:orientation="vertical">
		<!-- TextView for the heading of the activity -->
		<TextView
			android:id="@+id/textView"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@string/heading"
			android:textAlignment="center"
			android:textColor="@android:color/holo_green_dark"
			android:textSize="36sp"
			android:textStyle="bold" />
		<!-- EditText field for the Email -->
		<EditText
			android:id="@+id/inEmail"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="10dp"
			android:layout_marginTop="60dp"
			android:layout_marginEnd="10dp"
			android:layout_marginBottom="20dp"
			android:hint="@string/email_hint"
			android:inputType="textEmailAddress"
			android:padding="8dp"
			android:text="@={viewModel.userEmail}" />
		<!-- EditText field for the password -->
		<EditText
			android:id="@+id/inPassword"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="10dp"
			android:layout_marginEnd="10dp"
			android:hint="@string/password_hint"
			android:inputType="textPassword"
			android:padding="8dp"
			android:text="@={viewModel.userPassword}" />
		<!-- Login Button of the activity -->
		<Button
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginStart="20dp"
			android:layout_marginTop="60dp"
			android:layout_marginEnd="20dp"
			android:background="#4CAF50"
			android:fontFamily="@font/roboto"
			android:onClick="@{()-> viewModel.onButtonClicked()}"
			android:text="@string/button_text"
			android:textColor="@android:color/background_light"
			android:textSize="30sp"
			android:textStyle="bold"
			bind:toastMessage="@{viewModel.toastMessage}" />
		<ImageView
			android:id="@+id/imageView"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginTop="135dp"
			app:srcCompat="@drawable/banner" />
	</LinearLayout>
</layout>


گام ششم – ساخت لایه های کلاس ViewModel

این کلاس شامل همه متد هایی میشود که در که باید در Layout اپلیکیشن فراخوانی شوند. یک کلاس به نام ViewModel میسازیم که کلاس BaseObserver را extend میکند. این کلاس داده ها را به جریان تبدیل میکند و زمانی که نیاز بود پیام Toast تغییر کند، به لایه View اطلاع میدهد.

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 variable
	@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;
	}
}

گام هفتم – تعریف عملکرد های لایه View در فایل MainActivity

لایه View مسئول به روز رسانی رابط کاربری نرم افزار است. با توجه به تغییراتی که توسط ViewModel در پیام Toast رخ میدهد، آداپتر Binder لایه View را خبردار خواهد کرد. یعنی متد setter مربوط به Toast، Observer مخصوص خودش را خبردار میکند (که میشه همون لایه View) که تغییراتی در داده ها رخ داده است. بعد از این اتفاق، کارهای مناسب را انجام خواهیم داد.

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();
	}
}

گام هشتم – خروجی

بعد از انجام همه کارهایی که گفته شد، میتوانید پروژه خودتان را Build کنید و آن را روی دستگاه فیزیکی یا شبیه ساز مجازی اجرا کنید.


روش سوم: استفاده از LiveData و DataBinding

به عنوان روش سوم پیاده سازی معماری MVVM میخواهیم همین پروژه با استفاده از LiveData به همراه DataBinding بسازیم. از آنجایی که مراحل کار با روش های قبلی تفاوت زیادی ندارد، توضیحات هر مرحله را بصورت خلاصه نوشتیم. گام اول، دوم، سوم و چهارم دقیقا مانند گام های گفته شده در روش دوم هستند، بنابراین از گام پنجم شروع میکنیم.


گام پنجم – ساخت کلاس ViewModel

در این مرحله کلاس مربوط به لایه ViewModel به نام LoginViewModel را میسازیم. این کلاس با استفاده از DataBinding، اطلاعات مورد نیاز خودش را مستقیما از فایل XML اکتیوتی (که توی مرحله های بعدی میسازیمش) دریافت میکند.

import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.view.View;
public class LoginViewModel extends ViewModel {
    public MutableLiveData<String> EmailAddress = new MutableLiveData<>();
    public MutableLiveData<String> Password = new MutableLiveData<>();
    private MutableLiveData<LoginUser> userMutableLiveData;
    public MutableLiveData<LoginUser> getUser() {
        if (userMutableLiveData == null) {
            userMutableLiveData = new MutableLiveData<>();
        }
        return userMutableLiveData;
    }
    public void onClick(View view) {
        LoginUser loginUser = new LoginUser(EmailAddress.getValue(), Password.getValue());
        userMutableLiveData.setValue(loginUser);
    }
}

گام ششم – ساخت اکتیویتی (مربوط به لایه View)

یک اکتیویتی خالی به پروژه اضافه میکنیم (اگر روش ساختن اکتیویتی رو بلد نیستین، حتما این مقاله رو مطالعه کنین: اکتیویتی ها در اندروید).


گام هفتم – ویرایش فایل activity_main.xml (مربوط به لایه View)

در این فایل EditText هایی که کاربر داده های خود را درون آن وارد میکند را تعریف میکنیم. همچنین استفاده از DataBinding هم باید در این فایل تعریف شود. دقت کنید وقتی از تگ <data> استفاده میکنیم، دیگر نباید Constraint Layout ریشه فایل XML ما باشد. کدهای زیر را ببینید.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="LoginViewModel"
            type="com.example.umangburman.databindingwithlivedata.ViewModel.LoginViewModel" />
    </data>
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:isScrollContainer="true">
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".View.MainActivity">
            <TextView
                android:id="@+id/lblTitle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="16dp"
                android:lineSpacingExtra="8sp"
                android:text="Login Example Using MVVM, DataBinding with LiveData"
                android:textAlignment="center"
                android:textSize="18sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
            <EditText
                android:id="@+id/txtEmailAddress"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="32dp"
                android:layout_marginStart="32dp"
                android:layout_marginTop="32dp"
                android:ems="10"
                android:hint="E-Mail Address"
                android:inputType="textEmailAddress"
                android:text="@={LoginViewModel.EmailAddress}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/lblTitle" />
            <EditText
                android:id="@+id/txtPassword"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="32dp"
                android:layout_marginStart="32dp"
                android:layout_marginTop="16dp"
                android:ems="10"
                android:hint="Password"
                android:inputType="textPassword"
                android:text="@={LoginViewModel.Password}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/txtEmailAddress" />
            <Button
                android:id="@+id/btnLogin"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="32dp"
                android:layout_marginStart="32dp"
                android:layout_marginTop="32dp"
                android:text="Click to Login"
                android:onClick="@{(v) -> LoginViewModel.onClick(v)}"
                android:textSize="18sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/txtPassword" />
            <TextView
                android:id="@+id/lblTagline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="60dp"
                android:text="See the Results Below From LiveDataBinding"
                android:textColor="@android:color/background_dark"
                android:gravity="center"
                android:textSize="16sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/btnLogin" />
            <TextView
                android:id="@+id/lblEmailAnswer"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                android:text="---"
                android:textColor="@android:color/holo_blue_light"
                android:textSize="20sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/lblTagline" />
            <TextView
                android:id="@+id/lblPasswordAnswer"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="32dp"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                android:text="---"
                android:textColor="@android:color/holo_blue_light"
                android:textSize="20sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/lblEmailAnswer" />
        </android.support.constraint.ConstraintLayout>
    </android.support.v4.widget.NestedScrollView>
</layout>

گام هشتم – ویرایش کلاس MainActivity (مربوط به لایه View)

به عنوان گام آخر برای پیاده سازی معماری MVVM باید کلاس اکتیویتی خودمان را ویرایش کنیم.

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
public class MainActivity extends AppCompatActivity {
    private LoginViewModel loginViewModel;
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
        binding.setLifecycleOwner(this);
        binding.setLoginViewModel(loginViewModel);
        loginViewModel.getUser().observe(this, new Observer<LoginUser>() {
            @Override
            public void onChanged(@Nullable LoginUser loginUser) {
                if (TextUtils.isEmpty(Objects.requireNonNull(loginUser).getStrEmailAddress())) {
                    binding.txtEmailAddress.setError("Enter an E-Mail Address");
                    binding.txtEmailAddress.requestFocus();
                }
                else if (!loginUser.isEmailValid()) {
                    binding.txtEmailAddress.setError("Enter a Valid E-mail Address");
                    binding.txtEmailAddress.requestFocus();
                }
                else if (TextUtils.isEmpty(Objects.requireNonNull(loginUser).getStrPassword())) {
                    binding.txtPassword.setError("Enter a Password");
                    binding.txtPassword.requestFocus();
                }
                else if (!loginUser.isPasswordLengthGreaterThan5()) {
                    binding.txtPassword.setError("Enter at least 6 Digit password");
                    binding.txtPassword.requestFocus();
                }
                else {
                    binding.lblEmailAnswer.setText(loginUser.getStrEmailAddress());
                    binding.lblPasswordAnswer.setText(loginUser.getStrPassword());
                }
            }
        });
    }
}

نکته: همانطور که در این کدها مشاهده میکنید، به دلیل استفاده از DataBinding کار ما راحت تر شده و دیگر خبری از متد findViewById نیست.


سوالی دارید؟

سوال

در این مقاله معماری MVVM، لایه ها و ساختار آن و همچنین روش های پیاده سازی این ساختار در اپلیکیشن های اندروید را یاد گرفتیم. اگر سوالی در ذهن شما وجود دارد یا میتوانید نکته دیگری به این نوشته اضافه کنید، با نوشتن آن در قسمت نظرات (همین پایین) به کامل تر شدن این مقاله آموزشی کمک کنید.


منابع بیشتر برای مطالعه

میتوانید از منابع زیر برای مطالعه بیشتر درباره معماری MVVM اندروید استفاده کنید:

درباره نویسنده

9 در مورد “معرفی کامل معماری MVVM در اندروید + روش پیاده سازی”

  1. با درود و سلام،
    ببخشید یه سوالی برام پش اومده و اونم اینه که آرایه LoginUser که داخل لایه viewModel در روش آخر نوشته شده آیا منظورش لایه مدل است یا خیر؟ اگر نه منظورش چیست و از کجا اومده؟
    با تشکر فراوان از مقاله مفید و زحماتتون

  2. سلام یک سوال راجع به معماری ها داشتم اونم این که آیا توی پروژه های استاندارد و پروژه هایی که کلا میخوایم منتشر کنیم حتما باید از معماری استفاده کنیم؟

    1. سلام و خسته نباشید.
      بهتر هست که از ابتدا معماری ها رو توی پروژه نرم افزاری خودتون پیاده سازی کنین. چون این کار باعث میشه پروژه استاندارد تر و تمیز تری داشته باشین و بقیه همکاراتون بهتر بتونن ساختار پروژه رو بفهمن و اون رو توسعه بدن.

  3. به نام خدا، سلام و خسته نباشید مقاله مفیدی بود اول تشکر میکنم بابت زحمتی که برای تهیه اش کشیدید. دوم این که اگر میشه کد ها رو مثل بعضی از سایتا که هم رنگی هست و هم قابل کپی برداری کلی مثل گیت هاب، بزارید تا خواناییش بالاتر بره
    بازم ممنون

    1. سلام دوست عزیز.
      ممنونم از نظر خوبتون و توجهی که به متن داشتین.
      حتما این کار رو میکنیم که دوستان عزیزمون برای یادگیری، تجربه بهتری داشته باشن.
      با تشکر

  4. مرسی از مقاله خوبتون.
    یک سوالی داشتم:
    تو بعضی از سایت ها خوندم که میشه از RXjava و RxAndroidهم بعنوان روش های پیاده سازی معماری MVVMاستفاده کرد.
    آيا این درسته و اگه آره به چه صورت هست؟

    1. سلام و خسته نباشید خدمت شما دوست عزیز.
      بله میشه این کار رو کرد و به جای استفاده از LiveData میشه جریان های داده ای رو با استفاده از RxJava توی این معماری پیاده سازی کنیم.
      البته راهکار بهتری که میتونین به کار ببرین اینه که از LiveData استفاده کنین برای داده های مختلف و ارور ها رو با استفاده از RxJava به کامپوننت های دیگه برسونین.

دیدگاه‌ خود را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

اسکرول به بالا