Android Testing Part 2 — Mais MVP
No último post dei uma breve introdução sobre a importância dos testes em aplicações android e os dois tipos de testes que podemos efectuar em uma app nomeadamente Unit Tests e Instrumental Tests.
Falei ainda da importância e do objectivo desta série em criar de ambientes favoráveis para testes antes mesmo de começar a escrever código.
Ainda no post anterior fiz a introdução ao padrão de arquitectura MVP que de forma resumida pressupõe que a aplicação esta dividida em 3 componentes:
- View: Responsável pela apresentação da informação ao utilizador e todo outro tipo de operações relacionadas a interface gráfica.
- Model: Responsável pela representação de dados e mecanismos de escrita/leitura de dados a um determinado repositório(BD Local, Servidor).
- Presenter: Responsável por preparar os dados antes de serem apresentados pelo utilizador ou enviados para o repositório.
O presenter encontra-se no meio como o único mecanismo de comunicação entre o View e o Model.
Como aplicar o MVP em uma app?
Aplicar o MVP em uma app não tem nenhum segredo e temos de olhar para o projecto pelos seguintes pontos:
- Activities e Fragments — Irão representar a componente View da aplicação e serão responsáveis por realizar operações de UI. (Exemplo: Mostrar elementos, registar listeners nos botões, Iniciar novas Activities etc).
- Definir as operações que podemos realizar no presenter após receber um evento da View, criar uma interface e implementar os metodos em uma classe.
Esta interface será utilizada para efectuar a comunicação do view para o presenter. - Definir como os dados estarão representados e o mecanismos para a busca dos mesmos.
Tomando em consideração uma app com as simples funcionalidades de visualizar uma lista de eventos e posteriormente os detalhes do evento, aplicaríamos o padrão MVP da seguinte forma:
Organização de pacotes por funcionalidades
O primeiro passo consiste na mudança de como organizamos os pacotes e as nossas classes no projecto.
Para quem esteja a começar o desenvolvimento de android, normalmente não tem nenhuma organização estrita para os pacotes ou usa simplesmente a forma mais utilizada que consiste em organizar as classes em pacotes com componentes similares(Exemplo: Guardar todos os Fragment em um pacote de fragments ou Adapters em um pacote só com adapters).
Para facilmente aplicar o padrão MVP, recomendo que passemos a organizar os pacotes por funcionalidades da aplicação . (Exemplo: Todas as classes responsáveis pela funcionalidade de listar eventos no mesmo pacote)
com.app.eventos.blog → raiz do pacote
eventos → pacote com classes para listar eventos
evento → pacote com classes para visualização dos detalhes de um evento
login → pacote com as classes responsáveis pelo Login
Tendo os pacotes devidamente organizados, tomemos como exemplo para aplicar o MVP a funcionalidade de listar eventos seguindo os passos abaixo.
Criar um Contracto
Assumindo que temos uma ideia de como será a tela para listar eventos e sabemos como a UI irá se comportar e interagir com o utilizador, vamos primeiro criar uma interface que irá conter dentro duas interfaces denominadas View e UserActionsListener que serão responsáveis por definir os métodos que representam as operações a serem efectuadas pela View e pelo Presenter respectivamente.
public interface EventsContract {
interface View{
void showEvents(List<Event> events);
void showEmptyScreen(boolean isDataSetEmpty);
void showErrorScreen(boolean operationHasFailed);
void showProgressBar(boolean isLoading);
void showToast(@StringRes int messageResid);
void openEventDetailsUi(Event event);
}
interface UserActionsListener{
void loadEvent(EventsQueryForm queryForm);
void selectEvent(Event event);
}
}
Como mostra o código acima, a interface View define métodos que serão implementados pela View para realizar as diferentes operações como apresentar a lista de eventos, iniciar uma nova Activity com os detalhes do evento etc.
Por sua vez, a Interface UserActionsListener define os métodos a serem implementados pelo presenter que representam operações que originam da interacção do utilizador com a UI.
Criar o Presenter
Criar a classe que irá comportar-se como Presenter consiste em simplesmente criar uma classe que implementa a interface UserActionListener. Naturalmente ao implementar esta interface, todos os métodos definidos nela deverão ser obrigatoriamente implementados.
Como mostra a figura com a representação do padrão MVP no post anterior, o view e o presenter não se comunicam de forma directa mas sim através das interfaces definidas no contracto.
Sendo assim, temos de criar uma instância global no presenter com a interface da view que terá o seu valor atribuído passando a interface da view como parâmetro do construtor do presenter.
public class EventsPresenter implements EventsContract.UserActionsListener {
private EventsContract.View mView;
public EventsPresenter(EventsContract.View view, EventsRepoistory eventsRepoistory){
this.mView = view;
}
@Override
public void loadEvent(EventsQueryForm queryForm) {
mView.showProgressBar(true);
}
@Override
public void selectEvent(Event event) {
}
}
Criar a View
Como tinha mencionado acima, a escolha do MVP como arquitectura padrão foi baseada no objectivo de criar separação de tarefas entre os diferentes componentes de uma aplicação android e garantir que componentes como Fragments e Activities não ficassem cheios de código e lógica desnecessária e focassem-se apenas em realizar operações relacionadas a UI.
Para o nosso exemplo, a nossa View será um Fragment que simplesmente irá implementar a interface View e os seus métodos.
Estes métodos serão invocados pelo presenter para que a devida operação seja efectuada.
Se prestaram atenção ao último pedaço de código o presenter não tem nenhuma informação sobre como é efectuada a operação de mostrar ou não o ProgressBar assim como a View não tem noção de como e efectuado o loading dos eventos.
O código abaixo mostra o fragment que será responsável por apresentar a lista dos eventos . Este fragment implmenta a interface View e cria uma instância do UserActionListener onde passa a interface View como parâmetro do construtor.
public class EventsListFragment extends Fragment implements EventsContract.View {
private ProgressBar mProgressBar;
private ViewStub mErrorScreenStub;
private RecyclerView mRecyclerView;
private EventsListAdapter mAdapter;
private FragmentEventsListBinding mRootBinding;
private EventsContract.UserActionsListener mUsersActionListener;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsersActionListener = new EventsPresenter(this, Injection.provideEventsRepository());
mAdapter = new EventsListAdapter(mUsersActionListener);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
///Other code here return mRootBinding.getRoot();
}
@Override
public void showEvents(List<Event> events) {
}
@Override
public void showEmptyScreen(boolean isDataSetEmpty) {
}
@Override
public void showErrorScreen(boolean operationHasFailed) {
}
@Override
public void showToast(@StringRes int messageResid) {
String message = getResources().getString(messageResid);
Toast.makeText(getContext(),message,Toast.LENGTH_SHORT).show();;
}
@Override
public void openEventDetailsUi(Event event) {
}
@Override
public void showProgressBar(boolean isLoading) {
mProgessBar.setVisibility(View.VISIBLE);
}
}
Tendo a View e o Presenter prontos, o próximo passo seria a criação das classes que vão representar a componente Model do MVP.
Infelizmente não implementaremos o Model neste post pois acredito que faz mais sentido falar e mostrar a implementação no próximo ao introduzir o conceito de Dependency Injection.
Resumindo o post de hoje, com a implementação do MVP para o nosso exemplo da lista dos eventos podemos notar que:
- O Fragment responsável por apresentar a lista dos eventos apenas saberá realizar todas as operações relacionadas a UI.
- O presenter irá responsabilizar-se por realizar o processamento da informação antes desta ser apresentada a UI ou realizar alguma operação após alguma interacção com o utilizador.
- O View e o Presenter nunca comunicam-se directamente mas sim através de interfaces que definem métodos específicos para a comunicação.
Espero que ainda estejamos todos na mesma página e até a 2a(9 de Outubro) feira para mais um post a caminho de efectuar testes em um aplicação android.
Caso tenham alguma dúvida sobre esse post, não hesitem em deixar um comentário que ficarei feliz em esclarecer.
Ate a próxima =)
Se achas que este post foi útil por favor deixe um like clicando no coraçãozinho e partilhe com um amigo!