O padrão MVC é amplamente conhecido e utilizado quando nos referimos a desenvolvimento web. Mas, em relação a desenvolvimento mobile, o que utilizar? Como organizar e estruturar o código? A resposta é: podemos utilizar o MVC também, e é isso que iremos mostrar neste post.
O que é MVC?
MVC (Model-View-Controller) é um padrão de arquitetura que separa a informação e as regras de negócio da view e das interações do usuário. Ou seja, a View não irá acessar o Model diretamente, o Controller é o responsável por essa comunicação.
Abaixo vemos na imagem a interação entre Model-View-Controller:
O Model é o responsável por implementar as regras de negócio:
- recuperar informações, sabendo a origem dos dados,
- processar informações, aplicando as regras de negócio nos dados envolvidos, e
- armazenar informações, salvando os dados no destino correto.
A View é a responsável por exibir dados:
- apresentar informações, distribuindo e organizando os dados na tela, e
- interagir com o usuário, recebendo as ações executadas pelo usuário
O Controller é responsável por mediar as interações do usuário:
- reagir as interações do usuário,
- executar as regras de negócio, sabendo qual Model e quais informações ele precisa para essa execução, e
- repassar informações, mapeando os dados para os componentes da View fazerem a exibição.
Além desses 3 itens, é comum a utilização de uma classe para fazer o tráfego dos dados entre todos eles. Esse tipo de classe possui diversas denominações: Bean, Model, TO (Transfer Object) ou VO (Value Object).
Na imagem acima, podemos ver uma representação do MVC. Neste exemplo podemos ver a interação com a View, onde o usuário seleciona as peças desejadas e clica em um botão montar; em seguida o Controller irá processar essa interação e redirecionar a requisição ao Model para que o comando de montar seja executado. Neste passo, o Model aplica todas as regras de negócio em cima das informações para que consiga montar uma imagem a partir das peças recebidas. Esta imagem é repassada a View pelo Controller, para que o resultado da ação requisitada seja exibido ao usuário.
E o MVVM?
Uma alternativa ao MVC, é o MVVM (Model-View-ViewModel).
Na imagem abaixo, temos a interação entre Model-View-ViewModel:
Nesse padrão, temos algumas diferenças em relação ao MVC:
- Model: continua com as mesmas responsabilidades.
- View: além do que já foi listado acima, a View faz a atualização da tela a partir dos dados.
- Controller: não tem mais a responsabilidade de mapear os dados para os componentes da View, neste padrão o Controller apenas prepara os dados e a própria View realiza o mapeamento e a exibição.
Agora vamos dar exemplos de como utilizar o MVC nas tecnologias mobile. Esta é uma sugestão que pode ser adaptada a realidade de cada desenvolvimento.
O Model, independente da plataforma (Android, iOS e Windows Phone), é responsável pelas implementações das classes de:
- BO (Business Object), que contém as lógicas de negócio,
- Consultas a banco de dados, onde pode ser utilizado um design pattern, por exemplo: DAO (Data Access Object),
- Requisição de dados a servidor,
- Processamento de respostas, como parser,
- Processamento de dados, como ordenação, filtros
- Classes de Bean, Model, TO (Transfer Object) ou VO (Value Object).
MVVM no Windows Phone
O MVVM é um padrão consolidado na plataforma Windows, com o WPF e Windows Runtime, sendo utilizado desde os primeiros aplicativos Windows Phone 7.
A View, no Windows Phone é principalmente os XAMLs e os componentes visuais presentes são: ListView, Button, TextBlock…
E o ViewModel são as classes que fazem o tratamento das ações do usuário, além de preparar os dados recebidos do Model, gerando os bindables para que a View possa fazer a exibição.
MVC em Android
A View, no Android é principalmente os XMLs que contém o layout das telas, é a parte que exibe informações e recebe as ações do usuário. Os componentes visuais: TextView, EditText, ListView, RelativeLayout… fazem parte da View.
E o Controller são as classes que fazem a View “funcionar”, tratam as ações do usuário, preenchem os dados recebidos do Model na View para que sejam exibidos. No Android ficam no Controller: Activities e Fragments, Adapters, Listeners, AsyncTasks…
MVVM no Android M
O Android trouxe uma novidade na sua última versão, que permite o uso de uma alternativa do MVC, que é o MVVM.
No MVVM, a View utiliza o databinding para mapear os dados a partir de um objeto e o ViewModel, que substitui o Controller, converte os dados do formato vindo do Model, para o formato que a View consegue exibir.
MVC no iOS
A View, no iOS é composta pelos xibs e storyboards e os componentes visuais presentes são: UIView, UIButton, UITableView, entre outros.
No iOS ficam no Controller os ViewControllers.
Exemplo Prático
Agora vamos ver as diferenças de implementação entre um MVC e um MVVM. A plataforma utilizada foi Android, por ter suporte aos dois modelos, facilitando assim a comparação.
MVC
Model
Aqui foi implementado um BO que retorna uma lista de Beans. O BO implementa as regras de negócio do app: requisição, busca em banco de dados, processamento de dados, etc.
public class BO { public List<Bean> getData() { List<Bean> list = new ArrayList<>(); // regras de negócio return list; } }
public class Bean { private String mText; // dados do app public Bean(String text) { mText = text; } public String getText() { return mText; } }
Controller
Aqui foi implementado o Fragment e o Adapter que chamam o Model e tratam o retorno para exibir na tela. Abaixo, em destaque podemos ver as integrações Controller-Model e Controller-View.
public class MainActivityFragment extends Fragment { private final Adapter mAdapter; private MyAsyncTask mAt; public MainActivityFragment() { mAdapter = new Adapter(); } ... @Override public void onResume() { super.onResume(); loadData(); } private void loadData() { mAt = new MyAsyncTask(); mAt.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private class MyAsyncTask extends AsyncTask<Void, Void, List<Bean>> { @Override protected List<Bean> doInBackground(Void... voids) { BO bo = new BO(); return bo.getData(); } @Override protected void onPostExecute(List<Bean> beans) { mAdapter.setItems(beans); mAdapter.notifyDataSetChanged(); } } }
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> { private final List<Bean> mItems; ... @Override public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = new RecyclerViewItem(parent.getContext()); ViewHolder vh = new ViewHolder(v); return vh; } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.updateItem(mItems.get(position), mListener); } public static class ViewHolder extends RecyclerView.ViewHolder { public RecyclerViewItem mItemView; public ViewHolder(View v) { super(v); mItemView = (RecyclerViewItem) v; } public void updateItem(Bean bean, ItemClickListener listener) { mItemView.updateUI(bean); mItemView.setListener(listener); } } }
View
Aqui foi implementada uma classe dentro do Fragment e uma classe que representa o item da lista, que mapeiam e exibem os dados na tela. O XML com o layout do item da lista, é somente um layout tradicional do Android.
public class MainActivityFragment extends Fragment { private UI mUI; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View parentView = inflater.inflate(R.layout.fragment_main, container, false); mUI = new UI(parentView); mUI.toLoadingState(); return parentView; } ... protected class UI { private final RecyclerView mRecyclerView; private final ProgressBar mProgress; private final Button mButton; UI(View parentView) { mRecyclerView = (RecyclerView) parentView.findViewById(R.id.my_recycler_view); mProgress = (ProgressBar) parentView.findViewById(R.id.my_progress); mButton = (Button) parentView.findViewById(R.id.my_button); setupUI(); } void setupUI() { mRecyclerView.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.setAdapter(mAdapter); mButton.setOnClickListener(new MyButtonClickListener()); } void toLoadingState() { mRecyclerView.setVisibility(View.GONE); mProgress.setVisibility(View.VISIBLE); } void toNormalState() { mRecyclerView.setVisibility(View.VISIBLE); mProgress.setVisibility(View.GONE); } private class MyButtonClickListener implements View.OnClickListener { @Override public void onClick(View view) { Intent intent = new Intent(getActivity(), MainActivity2.class); getActivity().startActivity(intent); } } } }
public class RecyclerViewItem extends CardView { private UI mUI; private Bean mItem; private Adapter.ItemClickListener mListener; ... private void init() { LayoutInflater inflater = LayoutInflater.from(getContext()); View container = inflater.inflate(R.layout.fragment_main_recycler_view_item, this, false); mUI = new UI(container); addView(container); } public void updateUI(Bean item) { mItem = item; mUI.updateUI(); } private class UI { private final TextView mText; UI(View parentView) { mText = (TextView) parentView.findViewById(R.id.fragment_main_recycler_view_item_text); View view = parentView.findViewById(R.id.card_view); view.setOnClickListener(new ItemClickListener()); } private void updateUI() { if (null != mItem) { mText.setText(mItem.getText()); } } private class ItemClickListener implements View.OnClickListener { @Override public void onClick(View view) { if (mListener != null) { mListener.onItemClick(mItem); } } } } }
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/card_view" ...> <TextView android:id="@+id/fragment_main_recycler_view_item_text" .../> </android.support.v7.widget.CardView>
MVVM
Model
Para o MVVM, o Model continua sendo o mesmo mostrado acima, no MVC.
ViewModel
A mudança começa aqui. A implementação do Fragment continua como no MVC, e a do Adapter está um pouco diferente, como podemos ver abaixo nas partes em destaque.
public class Adapter2 extends RecyclerView.Adapter<Adapter.ViewHolder> { public class Adapter2 extends RecyclerView.Adapter<Adapter2.ViewHolder> { private final List<ViewBean> mItems; ... @Override public Adapter2.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final ListItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.list_item, parent, false); return new ViewHolder(binding.getRoot(), binding, mListener); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.bind(mItems.get(position)); } public static class ViewHolder extends RecyclerView.ViewHolder { private ListItemBinding mBinding; private ItemClickListener mListener; public ViewHolder(View v, ListItemBinding binding, final ItemClickListener listener) { super(v); mBinding = binding; mListener = listener; } public void bind(ViewBean s) { mBinding.setBean(s); mBinding.cardView.setOnClickListener(new MyClickListener()); } private class MyClickListener implements View.OnClickListener { @Override public void onClick(View view) { mListener.onItemClick(mBinding.getBean()); } } } }
View
A View também mudou um pouco. A classe dentro do Fragment continua existindo e está igual, porém não temos mais uma classe que representa o item da lista. O mapeamento para a exibição dos dados na tela, agora é implementado dentro do XML do item da lista como podemos ver destacado abaixo.
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="bean" type="br.talkitbr.example.viewmodel.bean.ViewBean" /> </data> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" ... > <TextView android:id="@+id/fragment_main_recycler_view_item_text" ..." android:text="@{bean.text}" /> </android.support.v7.widget.CardView> </layout>
O código completo está aqui. Nele temos a MainActivity com a implementação do MVC e a MainActivity2 com a implementação do MVVM.
- Pode ser que algumas classes pareçam não compilar, porém, o DataBinding gera classes em tempo de compilação, e mesmo com o Android Studio apontando falha na compilação, o exemplo executa.
Espero que tenhamos ajudado na organização do seu código e nos vemos no nosso próximo post!
#talkitbr