Android Architecture Patterns Part 2:Model-View-Presenter
It’s about time we developers start thinking about how we can apply good architecture patterns in our Android apps. Para ajudar com isso, o Google oferece projetos de arquitetura Android, onde Erik Hellman e eu trabalhamos juntos no MVP & RxJava sample. Vamos dar uma olhada em como aplicamos e os prós e contras desta abordagem.
Aqui estão as funções de cada componente:
- Modelo — A camada de dados. Responsável por lidar com a lógica de negócios e comunicação com as camadas de rede e banco de dados.
- ver-a camada UI. Mostra os dados e notifica o apresentador sobre as acções do utilizador.
- Presenter-recupera os dados do modelo, aplica a lógica UI e gerencia o estado da vista, decide o que exibir e reage às notificações de entrada do usuário da vista.
Uma vez que a vista e o apresentador trabalham em estreita colaboração, eles precisam ter uma referência um ao outro. Para tornar a unidade apresentadora testável com o JUnit, a vista é abstrata e uma interface para ela usada. A relação entre o apresentador e sua visão correspondente é definida em uma classe de interfaceContract
, tornando o código mais legível e a conexão entre os dois mais fáceis de entender.
Model-View-Presenter Padrão de & RxJava no Android Arquitetura de Projetos
O projeto de exemplo é um “Fazer” do aplicativo. Ele permite que um usuário crie, leia, atualize e delete tarefas “para fazer”, bem como aplicar filtros para a lista de Tarefas exibida. RxJava é usado para sair da linha principal e ser capaz de lidar com operações assíncronas.
modelo
o modelo trabalha com as fontes de dados locais e remotas para obter e salvar os dados. É aqui que a lógica empresarial é tratada. Por exemplo, ao solicitar a lista de Task
s, o modelo tentaria recuperá-los a partir da fonte de dados local. Se estiver vazio, ele irá consultar a rede, salvar a resposta na fonte de dados local e, em seguida, retornar a lista.a recuperação de Tarefas é feita com a ajuda de RxJava:
public Observable<List<Task>> getTasks(){
...
}
o modelo recebe como parâmetros nas interfaces construtoras das fontes de dados locais e remotos, tornando o modelo completamente independente de qualquer classe Android e, assim, fácil de testar por unidade com JUnit. Por exemplo, para testar se getTasks
pedidos de dados a partir do local de origem, implementamos o seguinte teste:
@Mock
private TasksDataSource mTasksRemoteDataSource;
@Mock
private TasksDataSource mTasksLocalDataSource;
...
@Test
public void getTasks_requestsAllTasksFromLocalDataSource() {
// Given that the local data source has data available
setTasksAvailable(mTasksLocalDataSource, TASKS);
// And the remote data source does not have any data available
setTasksNotAvailable(mTasksRemoteDataSource);
// When tasks are requested from the tasks repository
TestSubscriber<List<Task>> testSubscriber =
new TestSubscriber<>();
mTasksRepository.getTasks().subscribe(testSubscriber); // Then tasks are loaded from the local data source
verify(mTasksLocalDataSource).getTasks();
testSubscriber.assertValue(TASKS);
}
Exibir
O Vista funciona com o Apresentador para apresentar os dados e notifica o Apresentador sobre as ações do usuário. Em atividades MVP, fragmentos e visualizações personalizadas do Android podem ser vistas. A nossa escolha foi usar fragmentos.
Todas as vistas implementam a mesma interface BaseView que permite configurar um apresentador.
public interface BaseView<T> {
void setPresenter(T presenter);
}
A Vista notifica o Apresentador que está pronto para ser atualizado chamando o subscribe
método de o Apresentador onResume
. The View calls presenter.unsubscribe()
in onPause
to tell the Presenter that it is no longer interested in being updated. Se a implementação da vista é uma vista Android personalizada, então os métodos subscribe
e unsubscribe
os métodos têm de ser chamados em onAttachedToWindow
e onDetachedFromWindow
. As ações do usuário, como os cliques do botão, irão ativar os métodos correspondentes no apresentador, sendo este o que decide o que deve acontecer a seguir.
as vistas são testadas com Espresso. A tela de Estatística, por exemplo, precisa exibir o número de tarefas ativas e concluídas. O teste que verifica isso é feito corretamente, primeiro coloca algumas tarefas TaskRepository
; em seguida, inicia o StatisticsActivity
e verifica o conteúdo dos modos de exibição:
@Before
public void setup() {
// Given some tasks
TasksRepository.destroyInstance();
TasksRepository repository = Injection.provideTasksRepository( InstrumentationRegistry.getContext()); repository.saveTask(new Task("Title1", "", false));
repository.saveTask(new Task("Title2", "", true)); // Lazily start the Activity from the ActivityTestRule
Intent startIntent = new Intent();
mStatisticsActivityTestRule.launchActivity(startIntent);
}@Test
public void Tasks_ShowsNonEmptyMessage() throws Exception {
// Check that the active and completed tasks text is displayed
Context context = InstrumentationRegistry.getTargetContext();
String expectedActiveTaskText = context
.getString(R.string.statistics_active_tasks);
String expectedCompletedTaskText = context
.getString(R.string.statistics_completed_tasks); onView(withText(containsString(expectedActiveTaskText)))
.check(matches(isDisplayed()));
onView(withText(containsString(expectedCompletedTaskText)))
.check(matches(isDisplayed()));
}
o Apresentador
O Apresentador e sua correspondente Vista são criados pela Atividade. Referências à vista e ao TaskRepository
– O modelo – são dadas ao construtor do apresentador. Na implementação do construtor, o apresentador chamará o método setPresenter
da vista. Isto pode ser simplificado quando se utiliza uma estrutura de injeção de dependência que permite a injeção dos apresentadores nas vistas correspondentes, reduzindo o acoplamento das classes. A implementação do ToDo-MVP com Dagger é coberta por outra amostra.
Todos os apresentadores implementam a mesma interface BasePresenter.
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
When thesubscribe
method is called, the Presenter starts requesting the data from the Model, then it applies the UI logic to the received data and set it to the View. Por exemplo, no StatisticsPresenter
, todas as tarefas são solicitadas a partir do TaskRepository
– então as tarefas recuperadas são usadas para calcular o número de tarefas ativas e completadas. Estes números serão usados como parâmetros para o método showStatistics(int numberOfActiveTasks, int numberOfCompletedTasks)
da vista.
a unit test to check that indeed the showStatistics
method is called with the correct values is easy to implement. Estamos zombando doTaskRepository
e doStatisticsContract.View
e dar os objetos zombados como parâmetros para o construtor de umStatisticsPresenter
objeto. O teste de implementação é:
@Test
public void loadNonEmptyTasksFromRepository_CallViewToDisplay() {
// Given an initialized StatisticsPresenter with
// 1 active and 2 completed tasks
setTasksAvailable(TASKS); // When loading of Tasks is requested
mStatisticsPresenter.subscribe(); // Then the correct data is passed on to the view
verify(mStatisticsView).showStatistics(1, 2);
}
O papel da unsubscribe
método é limpar todas as assinaturas do Apresentador, evitando vazamentos de memória.
afora e unsubscribe
, cada apresentador expõe outros métodos, correspondendo às acções do utilizador na vista. Por exemplo, o AddEditTaskPresenter
, adiciona métodos como o createTask
, que seria chamado quando o usuário pressiona o botão que cria uma nova tarefa. Isso garante que todas as ações do Usuário – e, consequentemente, toda a lógica UI-passam pelo apresentador e, assim, podem ser testadas por unidade.
desvantagens do modelo-View-Presenter padrão
o modelo-View-Presenter padrão traz consigo uma separação muito boa de preocupações. Enquanto isso é de certeza um profissional, ao desenvolver um pequeno aplicativo ou um protótipo, isso pode parecer como uma sobrecarga. Para diminuir o número de interfaces usadas, alguns desenvolvedores removem a classe de interfaceContract
, e a interface para o apresentador.
uma das armadilhas de MVP aparece ao mover a lógica UI para o apresentador: esta se torna agora uma classe que sabe tudo, com milhares de linhas de código. Para resolver isso, divida o código ainda mais e lembre-se de criar classes que têm apenas uma responsabilidade e são testáveis por unidade.
conclusão
o padrão Model-View-Controller tem duas desvantagens principais: em primeiro lugar, A View tem uma referência ao controlador e ao modelo; e, em segundo lugar, não limita o tratamento da lógica UI a uma única classe, sendo esta responsabilidade partilhada entre o controlador e a vista ou o modelo. O padrão Model-View-Presenter resolve ambos os problemas quebrando a conexão que a View tem com o modelo e criando apenas uma classe que lida com tudo relacionado com a apresentação da View — o apresentador: uma única classe que é fácil de testar por unidade.e se quisermos uma arquitectura baseada em eventos, onde a vista reage às mudanças? Fique sintonizado para os próximos padrões amostrados nos projetos de arquitetura Android para ver como isso pode ser implementado. Até lá, leia sobre a implementação do padrão Model-View-View no aplicativo upday.