Padrões de arquitetura do Android, parte 3: Model-View-ViewModel

Tempo de leitura: 5 minutes

Após quatro designs diferentes nos primeiros seis meses de desenvolvimento do aplicativo de atualização, aprendemos uma lição importante: precisamos de um padrão de arquitetura que permita uma reação rápida às mudanças de design! A solução que escolhemos no final foi Model-View-ViewModel. Descubra comigo o que é MVVM; como o estamos aplicando no dia de hoje e o que o torna tão perfeito para nós.

 

O padrão Model-View-ViewModel

Os principais participantes do padrão MVVM são:

  • O View – que informa o ViewModel sobre as ações do usuário
  • O ViewModel – expõe fluxos de dados relevantes para o View
  • O DataModel – abstrai a fonte de dados. O ViewModel trabalha com o DataModel para obter e salvar os dados.

À primeira vista, o MVVM parece muito semelhante ao padrão Model-View-Presenter, porque ambos fazem um ótimo trabalho em abstrair o estado e o comportamento da visualização. O modelo de apresentação abstrai uma visualização independente de uma plataforma de interface de usuário específica, enquanto o padrão MVVM foi criado para simplificar a programação orientada a eventos de interfaces de usuário.

Se o padrão MVP significa que o Apresentador estava dizendo ao View diretamente o que exibir, no MVVM, ViewModel expõe fluxos de eventos aos quais os Views podem se ligar. Assim, o ViewModel não precisa mais conter uma referência para a View, como o Presenter. Isso também significa que todas as interfaces que o padrão MVP requer, agora são eliminadas.

Os Views também notificam o ViewModel sobre diferentes ações. Portanto, o padrão MVVM oferece suporte à vinculação de dados bidirecional entre View e ViewModel e há um relacionamento muitos para um entre View e ViewModel. View tem uma referência a ViewModel, mas ViewModel não tem informações sobre View. O consumidor dos dados deve saber sobre o produtor, mas o produtor – o ViewModel – não sabe, e não se importa, quem consome os dados.

 

Model-View-ViewModel na atualização

Uma rápida olhada nas postagens do Android no blog do upday revelará instantaneamente qual é a nossa biblioteca favorita: RxJava. Portanto, não é de admirar, porque RxJava é a espinha dorsal do código do upday! A parte orientada a eventos exigida pelo MVVM é feita usando Observáveis ​​do RxJava. Aqui está como aplicamos MVVM no aplicativo Android na atualização, com a ajuda de RxJava:

Modelo de dados

O DataModel expõe dados facilmente consumíveis por meio de fluxos de eventos – RxJava’s Observables. Ele compõe dados de várias fontes, como a camada de rede, banco de dados ou preferências compartilhadas e expõe os dados facilmente consumíveis para quem precisa. Os DataModels mantêm toda a lógica de negócios.

Nossa forte ênfase no princípio de responsabilidade única leva à criação de um DataModel para cada recurso no aplicativo. Por exemplo, temos um ArticleDataModel que compõe sua saída do serviço API e da camada de banco de dados. Este DataModel lida com a lógica de negócios garantindo que as últimas notícias do banco de dados sejam recuperadas, aplicando um filtro de idade.

 

ViewModel

O ViewModel é um modelo para a View do aplicativo: uma abstração da View. O ViewModel recupera os dados necessários do DataModel, aplica a lógica da IU e, em seguida, expõe os dados relevantes para o View consumir. Semelhante ao DataModel, o ViewModel expõe os dados por meio de Observables.

 

Aprendemos duas coisas sobre ViewModel da maneira mais difícil:

  • O ViewModel deve expor estados para a View, ao invés de apenas eventos. Por exemplo, se precisarmos exibir o nome e o endereço de e-mail de um usuário, em vez de criar dois fluxos para isso, criamos um objeto DisplayableUser que encapsula os dois campos. O stream será emitido sempre que o nome de exibição ou o e-mail mudar. Desta forma, garantimos que nossa Visualização sempre exiba o estado atual do usuário.
  • Devemos nos certificar de que toda ação do usuário passa pelo ViewModel e que qualquer lógica possível da View é movida para o ViewModel.

 

Escrevemos sobre esses dois tópicos em uma postagem de blog sobre erros comuns em MVVM + RxJava.

 

View

A Visualização é a interface do usuário real no aplicativo. Pode ser uma atividade, um fragmento ou qualquer visualização personalizada do Android. Para Activities and Fragments, estamos vinculando e desvinculando as fontes de eventos em onResume () e onPause ().

private final CompositeSubscription mSubscription = new CompositeSubscription();

@Override
public void onResume() {
    super.onResume();
    mSubscription.add(mViewModel.getSomeData()
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(this::updateView,
                            this::handleError));
}

@Override
public void onPause() {
   mSubscription.clear();
   super.onPause();
}

Se o MVVM View for um Android View customizado, a ligação é feita no construtor. Para garantir que a assinatura não seja preservada, levando a possíveis vazamentos de memória, a desvinculação ocorre em onDetachedFromWindow.

private final CompositeSubscription mSubscription = new CompositeSubscription();

public MyView(Context context, MyViewModel viewModel) {
      ...
      mSubscription.add(mViewModel.getSomeData()
                       .observeOn(AndroidSchedulers.mainThread())
                       .subscribe(this::updateView,
                                  this::handleError));
  }

  @Override
  public void onDetachedFromWindow() {
      mSubscription.clear();
      super.onDetachedFromWindow();
  }
}

 

Testabilidade das classes Model-View-ViewModel

Um dos principais motivos pelos quais amamos o padrão Model-View-ViewModel é que ele é tão fácil de testar.

 

DataModel

O uso do padrão de inversão de controle, fortemente aplicado em nosso código, e a falta de quaisquer classes Android, facilitam a implementação de testes unitários do DataModel.

 

ViewModel

Vemos as visualizações e os testes de unidade como dois tipos diferentes de consumidores de dados do ViewModel. O ViewModel é completamente separado da IU ou de qualquer classe Android, portanto, direto para o teste de unidade.

Considere o exemplo a seguir, em que ViewModel apenas expõe alguns dados do DataModel:

public class ViewModel {
    private final IDataModel mDataModel;

    public ViewModel(IDataModel dataModel) {
        mDataModel = dataModel;
    }

    public Observable<Data> getSomeData() {
        return mDataModel.getSomeData();
    }
}

Os testes para ViewModel são fáceis de implementar. Com a ajuda do Mockito, estamos mockando o DataModel e controlamos os dados retornados pelos métodos usados. Em seguida, garantimos que, ao assinar o Observable retornado por getSomeData (), os dados esperados sejam emitidos.

public class ViewModelTest {

@Mock
private IDataModel mDataModel;
private ViewModel mViewModel;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);    mViewModel = new ViewModel(mDataModel);
}

@Test
public void testGetSomeData_emitsCorrectData() {
    SomeData data = new SomeData();
            
    Mockito.when(mDataModel.getSomeData())
           .thenReturn(Observable.just(data));
    TestSubscriber<SomeData> testSubscriber = new TestSubscriber<>();    

    mViewModel.getSomeData().subscribe(testSubscriber);      
    testSubscriber.assertValue(data);
 }
}

Se o ViewModel precisa de acesso às classes Android, criamos wrappers que chamamos de Providers. Por exemplo, para recursos do Android, criamos um IResourceProvider, que expõe métodos como String getString (@StringRes final int id). A implementação do IResourceProvider conterá uma referência ao Context, mas o ViewModel fará referência apenas a um IResourceProvider injetado.

Como mencionamos acima, e em nossa postagem de blog com erros comuns, estamos criando objetos de modelo para manter o estado dos dados. Isso também permite um maior grau de testabilidade e controle dos dados que são emitidos pelo ViewModel.

 

View

Dado que a lógica na IU é mínima, as visualizações são fáceis de testar com o Espresso. Também estamos usando bibliotecas como DaggerMock e MockWebServer para melhorar a estabilidade de nossos testes de IU.

 

MVVM é a solução certa?

Estamos usando MVVM junto com RxJava por quase um ano. Vimos que, uma vez que View é apenas um consumidor de ViewModel, foi fácil apenas substituir diferentes elementos da IU, com alterações mínimas ou, às vezes, zero em outras classes.

Também aprendemos como a separação de interesses é importante e que devemos dividir mais o código, criando pequenas visualizações e pequenos ViewModels que têm apenas responsabilidades específicas. Os ViewModels são injetados nas Views. Isso significa que, na maioria das vezes, apenas adicionamos as visualizações na interface do usuário XML, sem fazer nenhuma outra alteração. Portanto, quando nossos requisitos de IU mudam novamente, podemos facilmente substituir algumas visualizações por novas.

 

Conclusão

O MVVM combina as vantagens da separação de interesses fornecidas pelo MVP, ao mesmo tempo que aproveita as vantagens das ligações de dados. O resultado é um padrão em que o modelo conduz o máximo de operações possível, minimizando a lógica na visualização.

Após as mudanças de design durante a “infância” de nosso aplicativo, mudamos para o MVVM na “adolescência” de atualização – um período de erros com o qual aprendemos muito. Agora, podemos nos orgulhar de um aplicativo que provou sua resistência a outro redesenho. Estamos finalmente perto de poder chamar o upday de um aplicativo maduro.

Um exemplo simples da implementação MVVM pode ser encontrado aqui. Um “Hello, World!” comparação entre MVP e MVVM pode ser encontrada aqui.