Шаблон проектирования Android MVVM

В этом уроке мы обсудим и реализуем Архитектурный шаблон MVVM для Android в нашем приложении Android. Ранее мы обсуждали Шаблон MVP для Android.

Зачем нам нужны эти шаблоны? Добавление всего в один Activity или Fragment может привести к проблемам при тестировании и рефакторинге кода. Поэтому рекомендуется использовать разделение кода и чистую архитектуру.

Android MVVM

MVVM означает Model, View, ViewModel.

  • Model: Здесь хранятся данные приложения. Он не может напрямую общаться с View. Обычно рекомендуется передавать данные в ViewModel через Observables.
  • View: Представляет собой пользовательский интерфейс приложения без какой-либо логики приложения. Он наблюдает за ViewModel.
  • ViewModel: Действует как связующее звено между Model и View. Он отвечает за преобразование данных из Model. Он предоставляет потоки данных для View. Он также использует хуки или обратные вызовы для обновления View. Он запросит данные из Model.

Следующий поток иллюстрирует основной шаблон MVVM. В чем разница с MVP?

  • ViewModel заменяет Presenter в среднем слое.
  • Presenter содержит ссылки на View. ViewModel этого не делает.
  • Presenter обновляет View с помощью классического способа (запуск методов).
  • ViewModel отправляет потоки данных.
  • Presenter и View находятся в отношении 1 к 1.
  • View и ViewModel находятся в отношении 1 к многим.
  • ViewModel не знает, что View слушает его.

Существует два способа реализации MVVM в Android:

  • Привязка данных
  • RXJava

В этом учебнике мы будем использовать только привязку данных. Библиотека привязки данных была представлена Google для прямого привязывания данных в макете xml. Для получения дополнительной информации о привязке данных смотрите этот учебник. Мы создадим простое приложение примера страницы входа, которое запрашивает ввод пользователя. Мы увидим, как ViewModel уведомляет View, когда показывать сообщение Toast без хранения ссылки на View.

Как можно уведомить некоторый класс, не имея ссылки на него? Это можно сделать тремя разными способами:

  • Используя двустороннюю привязку данных
  • Используя LiveData
  • Используя RxJava

Двустороннее связывание данных

Двустороннее связывание данных – это техника привязки ваших объектов к вашим макетам XML таким образом, что объект и макет могут взаимодействовать, отправляя данные друг другу. В нашем случае модель представления (ViewModel) может отправлять данные в макет и также отслеживать изменения. Для этого нам нужен BindingAdapter и пользовательский атрибут, определенный в XML. Адаптер привязки будет отслеживать изменения в свойстве атрибута. Мы узнаем больше о двустороннем связывании данных через наш пример ниже.

Пример структуры проекта Android MVVM

Добавление библиотеки Data Binding

Добавьте следующий код в файл build.gradle вашего приложения:

android {

    dataBinding {
        enabled = true
    }
}

Это активирует связывание данных в вашем приложении.

Добавление зависимостей

Добавьте следующие зависимости в ваш файл build.gradle :

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

Модель

Модель будет содержать электронную почту и пароль пользователя. Следующий класс 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 таким образом, что объект может отправлять данные в макет и наоборот. Синтаксис для двустороннего связывания данных выглядит так: @={переменная}

Макет

Ниже приведен код для 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>

Для использования привязки данных нам необходимо установить тег layout в верхней части. Здесь наша модель предоставляет данные для представления. ()-> viewModel.onLoginClicked() вызывает лямбда-слушатель клика кнопки, определенный в нашей модели представления. EditText обновляет значения в модели (через модель представления). bind:toastMessage="@{viewModel.toastMessage}" – это настраиваемый атрибут, который мы создали для двустороннего связывания данных. На основе изменений в toastMessage в модели представления вызывается BindingAdapter в представлении.

Модель представления

Ниже приведен код для 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. Нам нужно определить геттер и сеттер для пользовательского атрибута toastMessage, определенного в XML. Внутри сеттера мы уведомляем наблюдателя (который будет 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 генерируется автоматически из макета. Метод @BindingAdapter вызывается каждый раз, когда изменяется атрибут toastMessage, определенный на кнопке. Он должен использовать тот же атрибут, что и в XML и в ViewModel. Таким образом, в вышеприведенном приложении ViewModel обновляет модель, прослушивая изменения во View. Кроме того, модель может обновлять вид через ViewModel, используя notifyPropertyChanged. Результат работы вышеупомянутого приложения показан ниже: Это завершает этот учебный материал по Android MVVM с использованием DataBinding. Вы можете загрузить проект по ссылке ниже.

Основы AndroidMVVM

Ссылка на проект на GitHub

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