Padrão de Design MVVM do Android

Neste tutorial, estaremos discutindo e implementando o Padrão Arquitetural MVVM no nosso Aplicativo Android. Já discutimos anteriormente o Padrão MVP do Android.

Por que precisamos desses padrões? Adicionar tudo em uma única Activity ou Fragmento levaria a problemas em testar e refatorar o código. Portanto, o uso da separação de código e uma arquitetura limpa é recomendado.

Android MVVM

MVVM significa Modelo, Visualização, ViewModel.

  • Modelo: Isso mantém os dados da aplicação. Não pode se comunicar diretamente com a Visualização. Geralmente, é recomendado expor os dados para o ViewModel por meio de Observáveis.
  • Visualização: Representa a IU da aplicação, livre de qualquer Lógica da Aplicação. Observa o ViewModel.
  • ViewModel: Age como um link entre o Modelo e a Visualização. É responsável por transformar os dados do Modelo. Fornece fluxos de dados para a Visualização. Também usa ganchos ou callbacks para atualizar a Visualização. Solicitará os dados do Modelo.

O seguinte fluxo ilustra o padrão central MVVM. Como isso difere do MVP?

  • O ViewModel substitui o Apresentador na Camada Intermediária.
  • O Apresentador mantém referências para a Visualização. O ViewModel não.
  • O Apresentador atualiza a Visualização usando o método clássico (ativando métodos).
  • O ViewModel envia fluxos de dados.
  • O Apresentador e a Visualização estão em uma relação de 1 para 1.
  • A Visualização e o ViewModel estão em uma relação de 1 para muitos.
  • O ViewModel não sabe que a Visualização está ouvindo-o.

Há duas maneiras de implementar o MVVM no Android:

  • Vinculação de Dados
  • RXJava

Neste tutorial, usaremos apenas Vinculação de Dados. A Biblioteca de Vinculação de Dados foi introduzida pelo Google para vincular dados diretamente no layout xml. Para obter mais informações sobre Vinculação de Dados, consulte este tutorial. Criaremos um exemplo simples de aplicativo de página de login que solicita entradas do usuário. Veremos como o ViewModel notifica a Visualização quando mostrar uma mensagem Toast sem manter uma referência da Visualização.

Como é possível notificar uma classe sem ter uma referência dela? Isso pode ser feito de três maneiras diferentes:

  • Usando Vinculação de Dados Bidirecional
  • Usando Live Data
  • Usando RxJava

Two Way Data Binding

A Data Binding bidirecional é uma técnica de vincular seus objetos aos seus layouts XML de forma que o objeto e o layout possam enviar dados um ao outro. No nosso caso, o ViewModel pode enviar dados para o layout e também observar mudanças. Para isso, precisamos de um BindingAdapter e de um atributo personalizado definido no XML. O Binding Adapter irá escutar as mudanças na propriedade do atributo. Vamos aprender mais sobre a Data Binding bidirecional através do exemplo abaixo.

Estrutura do Projeto de Exemplo do Android MVVM

Adicionando a Biblioteca de Data Binding

Adicione o seguinte código ao arquivo build.gradle do seu aplicativo:

android {

    dataBinding {
        enabled = true
    }
}

Isso habilita o Data Binding em sua aplicação.

Adicionando as Dependências

Adicione as seguintes dependências no seu arquivo build.gradle:

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

Modelo

O Modelo irá conter o email e senha do usuário. A seguinte classe User.java faz isso:

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


}

A Data Binding bidirecional nos permite vincular objetos nos layouts XML de forma que o objeto possa enviar dados para o layout e vice-versa. A sintaxe para a data binding bidirecional é @={variável}

Layout

O código para o activity_main.xml é dado abaixo:

<?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>

A Data Binding requer que definamos a tag de layout no topo. Aqui, nosso ViewModel vincula os dados à View. ()-> viewModel.onLoginClicked() invoca o lambda do ouvinte de clique do botão definido em nosso ViewModel. O EditText atualiza os valores no Modelo (via ViewModel). bind:toastMessage="@{viewModel.toastMessage}" é um atributo personalizado que criamos para a data binding bidirecional. Com base nas alterações no toastMessage no ViewModel, o BindingAdapter seria acionado na View.

ViewModel

O código para o LoginViewModel.java é dado abaixo:

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

Os métodos chamados no layout estão implementados no código acima com a mesma assinatura. Se o método XML correspondente não existir, precisamos alterar o atributo para app:. A classe acima também pode estender ViewModel. Mas precisamos de BaseObservable, pois converte os dados em fluxos e notifica quando a propriedade toastMessage é alterada. Precisamos definir o getter e setter para o atributo personalizado toastMessage definido no XML. Dentro do setter, notificamos o observador (que será a View em nossa aplicação) que os dados foram alterados. A View (nossa atividade) pode definir a ação apropriada.

A classe BR é gerada automaticamente a partir do data binding quando você reconstrói o projeto

O código para a classe MainActivity.java é fornecido abaixo:

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

Graças ao DataBinding, a classe ActivityMainBinding é gerada automaticamente a partir do layout. O método @BindingAdapter é acionado sempre que o atributo toastMessage definido no Button é alterado. Ele deve usar o mesmo atributo definido no XML e no ViewModel. Portanto, na aplicação acima, o ViewModel atualiza o Modelo ouvindo as alterações na View. Além disso, o Modelo pode atualizar a view através do ViewModel usando o notifyPropertyChanged. A saída da aplicação acima em ação é mostrada abaixo: Isso encerra este tutorial sobre Android MVVM Usando DataBinding. Você pode baixar o projeto a partir do link fornecido abaixo.

AndroidMVVMBasics

Link do Projeto no Github

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