Imprimir o livro todoImprimir o livro todo

Livro 3 - Projeto Agenda de Contatos - Parte 2

Site: Moodle - IFSC
Curso: 2022-1 - FIC - Programação para Dispositivos Móveis - Prof. Bruno Calegaro
Livro: Livro 3 - Projeto Agenda de Contatos - Parte 2
Impresso por: Usuário visitante
Data: sexta, 18 Out 2024, 11:38

1. Apresentação

Neste livro 3 conheceremos novos componentes avançados para a construção de aplicativos usando o Android Jetpack. Vamos exemplificar o uso dos componentes de arquitetura (Android Arch Components) do Android Jetpack e o uso da biblioteca Room para a manipulação do banco de dados SQLite através do aplicativo de Agenda de Contatos.

O aplicativo Agenda de Contatos fornece acesso a informações de contato armazenadas em um banco de dados SQLite no dispositivo. No aplicativo podemos:

  • ver uma lista em ordem alfabética dos contatos
  • ver os detalhes de um contato tocando no nome dele na lista de contatos
  • adicionar novos contatos
  • editar ou excluir contatos existentes

Figura 1 Aplicativo Agenda de Contatos com múltiplas telas

Na primeira parte do projeto foram construídas as telas do aplicativo e as classes referentes a Atividade, Fragmentos, ViewModels, Adapters e classes necessárias para a configuração e execução do banco de dados. Devido ao tamanho do aplicativo, vamos revisar a finalidade de cada classe:

  • Pacote data
    • Contact: classe para conter a descrição de um objeto a ser manipulado pelo banco de dados.
    • ContactsDAO: classe padrão da biblioteca Room para conter as operações a ser manipuladas pelo banco de dados
    • ContactsDatabase: classe padrão da biblioteca Room para conter a descrição do banco de dado
    • ContactsRepository: está classe não é exigida pela biblioteca Room mas é usado devido boas práticas de programação pois ela irá oferecer a aplicação uma interface (API) para o restante do aplicativo.
  • Pacote principal
    • MainActivity: classe principal que gerencia os fragmentos do aplicativo e implementa seus métodos de interface de call-back para responder quando um contato é selecionado, um novo contato é adicionado ou um já existente é atualizado ou excluído
  • Pacote ui.main
    • MainFragment: essa classe gerencia a RecyclerView da lista de contatos e o FloatingActionButton para adicionar novos contatos.
    • MainViewModel: ViewModel para vincular os dados do banco a uma lista de contatos
    • ContactsAdapter:  subclasse de RecylerView.Adapter usada pelo ContactsFragment para vincular a lista ordenada de nomes de contato a RecyclerView.
    • ItemDivider: essa classe define o divisor exibido entre os itens da RecyclerView.
    • AddEditFragment:  essa classe gerencia os TextInputLayouts e um FloatingActionButton para adicionar um novo contato ou editar um já existente.
    • AddEditViewModel: ViewModel para armazenar os dados de um novo contato ou um já existente
    • DetailFragment: essa classe gerencia os componentes TextView estilizados que exibem os detalhes de um contato selecionado e os itens da barra de aplicativo que permitem ao usuário editar ou excluir o contato que está sendo exibi
    • DetailViewModel: ViewModel para vincular os dados de um contato específico

Nesta segunda parte do projeto, vamos fazer a configuração das interações entre os fragmentos com o componente de navegação Navigation e apresentação de dados fictícios.

Bons estudos!

2. Recursos Envolvidos

No desenvolvimento da segunda parte do aplicativo, vamos usar as seguintes tecnologias envolvidas na criação de aplicativos de múltiplas tela. Na primeira parte do projeto foram criados três Fragments  para o aplicativo. Nessa segunda parte, vamos lidar com a transição entre as telas e a configuração das interações entre os objetos. 

  • Navegação entre Fragmentos
    • A navegação se refere às interações que permitem aos usuários navegar, entrar e sair de diferentes partes do conteúdo no aplicativo. O componente de navegação do Android Jetpack ajuda a implementar a navegação, desde simples cliques em botões até padrões mais complexos, como barras de aplicativos e a gaveta de navegação.
    • Neste aplicativo vamos ter as seguintes transições de tela:
      • selecionar um contato para exibição;
      • tocar no FloatActionButton de adição (+) do fragmento da lista de contatos;
      • tocar nas ações de editar ou salvar o contato exibido no fragmento de detalhes;
      • tocar em salvar para finalizar a edição de um contato existente ou a adição de um novo contato
  • Componente de Navegação
    • Neste projeto vamos fazer o uso dos componentes de arquitetura disponível no Android Jetpack(pacote  androidx). Entre eles está o novo componente de navegação, Navigation, que oferece uma solução moderna e simples de se implementar, desde a configuração de gráfico de transições para a programação das transições.
    • O componente de navegação consiste de três partes principais, descritas abaixo:
      • Grafo de navegação: é um recurso XML que contém todas as informações relacionadas à navegação em um local centralizado. Isso inclui todas as áreas de conteúdo individual no aplicativo (telas), chamadas destinos, e todos os caminhos que podem ser percorridos pelo usuário no aplicativo. 
      • NavHost: é um contêiner vazio que mostra destinos do gráfico de navegação. O componente de navegação contém uma implementação NavHost padrão, NavHostFragment, que mostra os destinos do fragmento. Esse componente gráfico será adicionado a atividade principal e funcionará como o recipiente a ser preenchido com as telas do aplicativo.
      • NavController: é um objeto que gerencia a navegação do aplicativo em um NavHost. O NavController organiza a troca do conteúdo de destino no NavHost conforme os usuários se movem pelo aplicativo. 

3. Navegação

Neste projeto vamos fazer o uso do componente Navigation para configurar a transição das telas. Para tanto precisamos primeiro criar um novo gráfico de navegação e criarmos as transições entre as telas. Também precisamos configurar a atividade principal e programar as ações dos botões.

3.1. Criando um grafo de navegação

Para criar o grafo de navegação precisamos adicionar um novo recurso no projeto. Para tanto, clique com o botão direito sobre a pasta res e selecione New -> Android Resource File.

Na próxima janela preencha o nome do recurso como nav_graph e selecione o campos Resouce type como Navigation. Clique em OK

Provavelmente, será notificada uma janela de aviso dizendo para adicionar as dependências do novo componente ao projeto, clique em OK e aguarde. O componente Navigation pertence ao pacote androidx.navigation é deve ser adicionado ao projeto. Se você seguir as orientações do Android Studio isso ocorrerá automaticamente.

Com a nova biblioteca uma nova pasta para armazenar grafos de navegação foi criada. Se tudo estiver certo, ao abrir o arquivo nav_graph, localizado na pasta res/navigation será mostrada a seguinte interface gráfica.

A navegação pode ser configurar via arquivo xml ou graficamente, por simplicidade, iremos ilustrar graficamente como fazer o processo passo a passo. Para o projeto da Agenda de Contatos vamos adicionar primeiramente as telas dos fragmentos e depois fazer as transições entre elas.

3.2. Configurando o grafo

Inicialmente o grafo está em branco e não mostra nenhuma tela. Vamos adicionar a primeira tela como o MainFragment e consequentemente configurar ela como a tela inicial. A tela inicial pode ser configurada manualmente, mas como o grafo está em branco automaticamente ele vai definir a tela inicial como a primeira tela adicionada a navegação.

Clique no botão ilustrado na figura para adicionar um novo destino (New Destination) e selecione o fragmento principal do nosso projeto.

As possíveis telas serão mostradas como opção de destino. Selecione main_fragment para adicionar a tela ao grafo.

Após adicionar o fragmento principal o grafo se apresentará da seguinte forma, observe que está anotado Start mostrando que a tela principal da navegação será o fragmento adicionado.

Repita os passos anteriores e adicione as demais telas ao grafo. Como resultado final, após organizar a posição das telas você poderá ter a seguinte tela:

A próxima etapa é configurar as possíveis transições de tela pois a navegação se dará por meio das transições configuradas nesse momento. Assim selecione uma tela e clique sobre a bolinha azul que aparece na borda da tela, segure e clique na tela a qual será o destino da transições. Segue a ilustração de como fazer a transição do fragmento principal para o fragmento de editar:

Repeti isso para fazer mais duas transições:

  • Transição do fragmento principal ao fragmento de detalhes
  • Transição do fragmento de detalhes ao fragmento de adicionar/editar 

Como resultado final teremos o seguinte grafo:

Por questão de simplicidade iremos omitir opção avançadas da navegação como animações e definição de argumentos,  assim isso é tudo que você precisa fazer para terminar a configuração da transição das telas. Cada transição adicionado foi configurada automaticamente com o nome das telas origem e destino como: action_mainFragment_to_addEditFragmentaction_mainFragment_to_detailFragmentaction_detailFragment_to_addEditFragment.  Através do nome da ação, o NavController consegue efetuar a navegação entre as telas. Na próxima seção vamos ver como programar os eventos para chamar essas transições.

4. Programando as transições

Para fazer as transições entre as telas do aplicativo vamos acessar o NavController da atividade principal e invocar as ações das transições criadas no grafo. Por exemplo, ao se clicar no botão de adição de um novo contato (+) o fragmento ContactsFragment dispara a transição para ir ao AddEditFragment, o NavController se encarrega de realizar as operações necessárias para que a transição de telas ocorra de maneira fluída.

Nas próximas etapas vamos alterar o layout da atividade principal para incorporar o elemento NavHost para a ativar a navegação e atualizar a programação. Na sequência vamos configurar cada fragmento e vincular a ação dos botões com as transições de telas.

4.1. Classe MainActivity

A classe MainActivity deve hospedar o elemento NavHost e vincular o grafo de navegação. Toda as operações de transições de tela ficaram a cargo do componente Navigation então a programação deve ser atualizada também. 

4.1.1 Alteração no arquivo main_activity.xml

Precisamos adicionar o elemento NavHost a raiz do projeto dentro do layout da atividade principal. Assim abre o arquivo main_activity.xml e procure na paleta na aba ContainersNavHostFragment. Adicione ao layout principal o novo container.

Ao arrastar e soltar o novo elemento uma nova janela será aberta solicitando a escolha de qual grafo de navegação usar, selecione o grafo criado anteriormente, nav_graph, e clique em OK.

Como resultado final você deverá ver a tela inicial da navegação, MainFragment, sendo apresentada na atividade principal. Isso ocorre porquê a grafo configurou a tela inicial como está sendo assim não cabe mais a atividade principal controlar quais fragmentos devem ser mostrados. 

4.1.2 Atualização do arquivo MainActivity.java

Como tudo será gerenciado pelo NavController a partir de agora, é necessário limpar o código fonte da atividade principal porque ele está manualmente solicitando o carregamento do fragmento principal ao se iniciar a aplicação. Assim, abra o arquivo MainActivity.java e remova o código anterior que fazia o carregamento do fragmento principal usando o método getSupportFragmentManager. Também, deve ser adicionado a atividade principal duas constantes, CONTACT_ID NEW_CONTACT, que serão usadas posteriormente pelos fragmentos para fazer a passagem de argumentos na transição das telas. Como resultado o arquivo deve ficar como:

public class MainActivity extends AppCompatActivity {
//definição das constantes usadas na transição das telas
public static final String CONTACT_ID = "CONTACT_ID";
public static final int NEW_CONTACT = -1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
}
}

4.2. Classe MainFragment

A classe MainFragment exibe a lista de contatos em uma RecyclerView e fornece um FloatingActionButton para a adição de novos contatos.

4.2.1 Importações e Campos

O fragmento MainFragment possui uma ViewModel para vincular os dados de agenda de contatos a uma lista. Esse campo é importante pois será usado para configurar a integração dos dados do banco de dados com a RecyclerView. Renomeie a variável da ViewModel para mainViewModel pois posteriormente escreveremos os demais fragmentos de maneira similar. Também, para a configuração da transição das telas precisamos fazer o uso das constantes declaradas na classe MainActivity por isso certifique-se de colocar as importações corretas. 

import static com.bcalegaro.agendadecontatos.MainActivity.CONTACT_ID;
import static com.bcalegaro.agendadecontatos.MainActivity.NEW_CONTACT;

public class MainFragment extends Fragment {

private MainViewModel mainViewModel;

...
}

4.2.2 Método onCreateView

No método onCreateView vamos inicializar os componentes da interface gráfica definidos no arquivo de layout main_fragment.xml. Futuramente vamos vincular o adaptador a RecyclerView., mas nesta segunda etapa do projeto vamos apenas adicionar o tratamento de eventos do FAB.

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main_fragment, container, false);

// TODO recyclerView, adapter, itemdivider

// cria uma referencia ao FAB e vincula uma ação
FloatingActionButton addButton = view.findViewById(R.id.addButton);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Quando for clicado no FAB o método onAddContact será invocado
onAddContact();
}
});
return view;
}

4.2.3 Método onAddContact

O método onAddContact é disparado no toque do botão flutuante de adicionar um novo contato. Esse método deve usar a navegação para solicitar a transição da tela atual para o fragmento addEditFragment. Como se trata de um novo contato, será usada para a passagem de informações (argumentos) as constantes registradas na MainActivity.

private void onAddContact() {
//cria um pacote de argumentos
Bundle arguments = new Bundle();
// adiciona o ID do contato como argumento a ser passado ao fragmento
// como se trate de um novo contato o valor a ser encaminhado será NEW_CONTACT
arguments.putInt(CONTACT_ID, NEW_CONTACT);
//solitica a transição de tela para o fragmento de adicionar um novo contato
Navigation.findNavController(getView()).navigate(R.id.action_mainFragment_to_addEditFragment, arguments);
}

Para invocar uma nova tela, fragmento, usando o componente de arquitetura do Android Jetpack Navigation usamos as ações (action) definidas no grafo de navegação. A ação em questão é a da transição da tela mainFragment para addEditFragment. Além de informar qual ação o navegador deve executar podemos passar argumentos, dados que podem transmitidos de uma tela a outra.

A classe Bundle é um pacote de transmissão de informações entre os fragmentos e atividades que pode também ser utilizada como uma maneira de passar dados de um lado para o outro. No aplicativo da Agenda de Contatos as informações a serem compartilhadas entre as telas são o código único de cada contato. Neste caso específico, o botão adicionar deve enviar um código para o fragmento addEditFragment que sinaliza que se trata de um novo contato a ser inserido. Assim, o novo fragmento ao receber o valor NEW_CONTACT consegue se adaptar a situação de adição de um novo contato e não a edição de um contato já existente. Para fazer o registro de um novo argumento ao pacote (bundle) se usa o método putInt para registrar um número inteiro com a chave CONTACT_ID

4.2.4 Método onContactSelected

O método onContactSelected é disparado no toque de um item da lista de contatos e usa a navegação para solicitar a transição da tela atual para a tela de detalhes, fragmento detailFragment. Esse método será vinculado a lista e operará da seguinte forma, ao se tocar no item da lista de contatos, a lista invoca o método passando como argumento para a função o código do contato selecionado, contactID (o código de um contato é um valor de identificação única usado no banco de dados, similar ao RG das pessoas, só pode haver um), navegador, então, encaminha o código do contato para o fragmento de detalhes, onde será exibido todas as informações do contato selecionado.

// chamado quando o contato é selecionado
private void onContactSelected(int contactID){
//cria um pacote de argumentos
Bundle arguments = new Bundle();
// adiciona o contactID informado como argumento a ser passado ao fragmento
arguments.putInt(CONTACT_ID, contactID);
//solitica a transição de tela para o fragmento de detalhes do contato
Navigation.findNavController(getView()).navigate(R.id.action_mainFragment_to_detailFragment, arguments);
}

4.2.5 Método onActivityCreated

O método de ciclo de vida onActivityCreated de Fragment é chamado depois que a atividade hospedeira de um fragmento foi criada e o método onCreateView terminou de executar. Usamos esse método para configurar a ViewModel. No entanto, nesta segunda parte do projeto apenas arrumar a nomenclatura do campo para conferir com mainViewModel.

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
// TODO: Use the ViewModel
}

4.3. Classe AddEditFragment

A classe AddEditFragment fornece uma interface para adicionar novos contatos ou editar os já existentes.

4.3.1 Importação e Campos

O fragmento AddEditFragment possui variáveis para fazer a referência aos seus componentes gráficos pois o usuário deve digitar três campos: nome, telefone e e-mail. Além disso, a mesma interface é usada para adicionar e editar um contato existente por isso será usada uma tag para sinalizar quando o fragmento deve ler os dados de um contato já existente e iniciar sua edição ou apenas criar um contato novo. O fragmento usará a ViewModel para manipular os dados do contato já existente ou novo. Também, como no fragmento anterior é necessária a adição das importações das constantes para configuração da transição das telas precisamos.

import static br.com.bcalegaro.agendacontatos.MainActivity.CONTACT_ID;
import static br.com.bcalegaro.agendacontatos.MainActivity.NEW_CONTACT;

public class AddEditFragment extends Fragment {
private int contactID; // ID do contato selecionado
private boolean addingNewContact; // flag para sinalizar adição ou edição

// componente FAB para salvar o contato
private FloatingActionButton saveContactFAB;

// ViewModel do fragmento
private AddEditViewModel addEditViewModel;

4.3.2 Método onCreateView

No método onCreateView vamos inicializar os componentes da interface gráfica definidos no arquivo de layout add_edit_fragment. Futuramente vamos obter as referências aos componentes da interface gráfica. Mas para esta segunda parte do projeto vamos apenas vincular o tratamento de eventos do FAB.

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// cria o fragmento com o layout do arquivo add_edit_fragment.xml
View view = inflater.inflate(R.layout.add_edit_fragment, container, false);

// TODO componentes TextInputLayout

// configura o receptor de eventos do FAB
saveContactFAB = view.findViewById(R.id.saveButton);
saveContactFAB.setOnClickListener(saveContactButtonClicked);
// acessa a lista de argumentos enviada ao fragmento em busca do ID do contato
Bundle arguments = getArguments();
contactID = arguments.getInt(CONTACT_ID);
// verifica se o fragmento deve criar um novo contato ou editar um já existente
if (contactID == NEW_CONTACT) {
// usa a flag para sinalizar que é um novo contato
addingNewContact = true;
} else {
// usa a flag para sinalizar que é uma edição
addingNewContact = false;
}
return view;
}

Ao se criar o fragmento, criamos um objeto arguments da classe Bundle para conseguirmos ler as informações repassadas pelo NavController na transição de telas. Buscamos o argumento com a chave CONTACT_ID e verificamos seu valor, assim conseguimos detectar se o fragmento foi invocado para uma operação de adição ou edição de um contato. A tag addingNewContact será usada na parte 3 do projeto, vamos programar para que em caso de edição os dados do contato existente sejam carregados do banco de dados e apresentados nos campos de edição. Caso contrário, por se tratar de um contato novo, a interface deve apresentar os campos de edição em branco.

4.3.3 Implementação de interface onClickListener com saveContactButtonClicked

O tratamento de eventos para o FAB do fragmento, botão flutuante salvar (saveButton), é cadastrado como a implementação da interface saveContactButtonClicked. O código executado acessa o sistema nativo do Android parar solicita para esconder o teclado virtual (hideSoftInputFromWindow). Por fim, é invodado o método saveContact para efetivamente salvar o novo contato.

private final View.OnClickListener saveContactButtonClicked = new View.OnClickListener() {
@Override
public void onClick(View view) {
// oculta o teclado virtual
((InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
getView().getWindowToken(), 0);
saveContact();
}
};

4.3.4 Método saveContact

O método saveContact deve ler os dados preenchidos e salvar o contato criado/editado. Sua implementação completa se dará na parte 3 deste projeto. Por ora, vamos fazer apenas a configuração da transição de telas que neste caso é solicitar ao NavController voltar uma tela com o método popBackStack.

// salva informações de um contato no banco de dados
private void saveContact() {
// TODO ler dados inseridos
// TODO salvar dados
// solicita o retorno a tela anterior
Navigation.findNavController(getView()).popBackStack();
}

O BackStack é uma pilha, como o próprio nome diz, que armazena estados das telas em relação as transações. O BackStack permite, de forma transparente, a navegação entre os fragmentos ou atividades decorrente do empilhamento desses. O elemento no topo da pilha sempre será a tela atual, assim, remover o elemento do topo significa você voltar para a tela anterior, pop é ação de remover o estado no topo da pilha. Assim, não é necessário executar uma ação identificando origem e destino na navegação neste caso, apenas voltar uma tela é o suficiente.. 

4.3.5 Método onActivityCreated

O método de ciclo de vida onActivityCreated de Fragment é chamado depois que a atividade hospedeira de um fragmento foi criada e o método onCreateView terminou de executar. Usamos esse método para configurar a ViewModel. No entanto, nesta segunda parte do projeto apenas arrumar a nomenclatura do campo para conferir com addEditViewModel.

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
addEditViewModel = new ViewModelProvider(this).get(AddEditViewModel.class);
// TODO: Use the ViewModel
}

4.4. Classe DetailFragment

A classe DetailFragment fornece os detalhes de um contato selecionado.

4.4.1 Importações e Campos

O fragmento DetailFragment deve apresentar os dados de um contato específico portante deve armazenar o código do contato selecionado em uma variável. Além disso, para prosseguir com as transições entre telas é preciso fazer a importação correta da constante CONTACT_ID presente na atividade principal. 

import static com.bcalegaro.agendadecontatos.MainActivity.CONTACT_ID;

public class DetailFragment extends Fragment {
private int contactID; // ID do contato selecionado
// TODO componentes TextView
private DetailViewModel detailViewModel; // ViewModel do fragmento

O fragmento usará a ViewModel para manipular os dados do contato selecionado.

4.4.2 Método onCreateView

O método onCreateView configura o fragmento para possuir um menu e faz a leitura do ID do contato recebido. A leitura do contato recebido é realizada com o objeto arguments da classe Bundle.

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// cria o fragmento com o layout do arquivo details_fragment.xml
View view = inflater.inflate(R.layout.detail_fragment, container, false);
// configura o fragmento para exibir itens de menu
setHasOptionsMenu(true);
// TODO componentes textview
// acessa a lista de argumentos enviada ao fragmento em busca do ID do contato
Bundle arguments = getArguments();
if (arguments != null)
contactID = arguments.getInt(CONTACT_ID);
return view;
}

Para vincular um menu a uma atividade/fragmento é preciso ativar a opção através do método setHasOptionsMenu. A correta configuração do menu será realizada através de dois métodos auxiliares onCreateOptionsMenu e onOptionsItemSelected, implementados logo a seguir.

4.4.3 Métodos onCreateOptionsMenu e onOptionsItemSelected

O método onCreateOptionsMenu defini qual menu será adicionado à atividade ou fragmento e inicia seu carregamento. Para este fragmento vamos usar recurso xml criado na parte 1 do projeto chamado fragment_details_menu. Para programar a ação a ser executada ao se clicar nos ícones do menu precisamos implementar o método onOptionsItemSelected. Esse método seleciona qual item de menu foi clicado e encaminha para a ação desejada.

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
// seleciona o menu a ser mostrado no fragmento
inflater.inflate(R.menu.detail_fragment_menu, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_edit:
editContact();
return true;
case R.id.action_delete:
deleteContact();
return true;
}
return super.onOptionsItemSelected(item);
}

4.4.4 Método editContact

Quando o usuário tocar item de menu para excluir um contato o método editContact é invocado. Esse método solicita ao NavController a transição para o fragmento addEditFragment repassando o ID do contato.

// passa o ID do contato para editar no DetailFragmentListener
private void editContact(){
//cria um pacote de argumentos
Bundle arguments = new Bundle();
// adiciona o ID do contato como argumento a ser passado ao fragmento
arguments.putInt(CONTACT_ID, contactID);
//solitica a transição de tela para o fragmento de editar um novo contato
Navigation.findNavController(getView()).navigate(R.id.action_detailFragment_to_addEditFragment, arguments);
}

4.4.5 Método deleteContact

Quando o usuário tocar no item de menu para excluir um contato, o método deleteContact é invocado. Sua implementação completa se dará na parte 3 deste projeto, por ora, vamos programar apenas a transição de tela que neste caso será a de voltar a tela anterior. Novamente, não é necessário acionar uma ação no NavController, apenas invocar o método popBackStack.

// exclui um contato
private void deleteContact() {
// TODO deletar contato
// solicita o retorno a tela anterior
Navigation.findNavController(getView()).popBackStack();
}

4.4.6 Método onActivityCreated

O método de ciclo de vida onActivityCreated de Fragment é chamado depois que a atividade hospedeira de um fragmento foi criada e o método onCreateView terminou de executar. Usamos esse método para configurar a ViewModel. No entanto, nesta segunda parte do projeto apenas arrumar a nomenclatura do campo para conferir com detailViewModel.

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
detailViewModel = new ViewModelProvider(this).get(DetailViewModel.class);
// TODO: Use the ViewModel
}

5. Manipulação de Listas

Para finalizar a segunda parte do aplicativo vamos configurar o elemento RecyclerView do fragmento da tela inicial do aplicativo. A lista deve exibir o nome dos contatos armazenados e ao se clicar em um item deve ir para o fragmento de detalhes.

Para essa finalidade será configurada as três classes:

  • Contact - Para representar os dados de um contato a serem exibidos na tela
  • ContactsAdapter - Para vincular os dados de uma lista de contatos a uma RecyclerView
  • ItemDivider - Elemento gráfico para desenhar uma linha de separação entre os nomes da lista de contato

5.1. Classe Contact

A classe Contact faz parte do pacote data e abstrai as informações de um contato. Para a segunda parte do aplicativo vamos criar o classe para conter o id, nome, telefone e e-mail. Ademais, devem ser adicionados os referidos métodos get e o construtor padrão.

public class Contact {
private int id;
private String name;
private String phone;
private String email;

public Contact(int id, String name, String phone, String email) {
this.id = id;
this.name = name;
this.phone = phone;
this.email = email;
}

public int getId() {
return this.id;
}

public String getName() {
return this.name;
}

public String getPhone() {
return this.phone;
}

public String getEmail() {
return this.email;
}
}

Essa classe vai ser usada para criar uma lista de contatos e futuramente será definida com anotações da biblioteca Room para especificar uma tabela do banco de dados.

5.2. Classe ContactsAdapter

Ao se lidar com listas de itens no Android um adaptador deve ser implementado, ele é o responsável por vincular uma lista de valores aos elementos gráficos de uma ListView. No caso específico de uma RecyclerView o adaptador deve ser implementado com a herança da classe RecyclerView.Adapter. Neste projeto, criamos o ContactsAdapterpara preencher o conteúdo da RecyclerView da aplicação.

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactsViewHolder> {
// interface implementada por ContactsFragment para responder
// quando o usuário toca em um item na RecylerView
public interface ContactClickListener {
void onClick(int contactID);
}

public class ContactsViewHolder extends RecyclerView.ViewHolder {
private final TextView contactItemView;
private int contactID;

private ContactsViewHolder(View itemView) {
super(itemView);
contactItemView = itemView.findViewById(android.R.id.text1);
// anexa receptor a itemView
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clickListener.onClick(contactID);
}
});
}
// configura o identificador de linha do banco de dados para o contato
public void setContactID(int contactID) {
this.contactID = contactID;
}
}

private List<Contact> mContacts; // Cached copy of contacts
private final ContactClickListener clickListener;

// construtor
public ContactsAdapter(ContactClickListener clickListener) {
this.clickListener = clickListener;
}

@Override
public ContactsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// infla o layout android.R.layout.simple_list_item_1
View view = LayoutInflater.from(
parent.getContext()).inflate(
android.R.layout.simple_list_item_1,
parent, false);
return new ContactsViewHolder(view);
}

@Override
public void onBindViewHolder(ContactsViewHolder holder, int position) {
if (mContacts != null) {
Contact current = mContacts.get(position);
holder.contactItemView.setText(current.getName());
holder.setContactID(current.getId());
}
}

void setContacts(List<Contact> contacts){
mContacts = contacts;
notifyDataSetChanged();
}

@Override
public int getItemCount() {
if (mContacts != null)
return mContacts.size();
else return 0;
}
}

Dentro desta classe também serão definidos os seguintes elementos:

  • Interface ContactClickListener
    • Interface que a classe ContactsFragment implementa para ser notificada quando o usuário toca em um contato da RecyclerView
    • Cada item da lista, pode disparar esse evento. Ao se clicar em um contato, o método deve notificar a MainActivity que um contato foi selecionado, para que a atividade principal possa exibir o componente DetailFragment
  • Classe ContactsViewHolder
    • Classe para usar o padrão ViewHolder. Utiliza a herança do elemento RecyclerView.ViewHolder.
    • ViewHolder é um elemento que armazena os dados do item em cada posição da lista. No caso do contato, cada ViewHolder vai armazenar o ID do contato e assimilar a ação onClick indicando esse valor de ID.
  • Método onCreateViewHolder
    • Método que infla a interface gráfica usando um layout predefinido no Android. O android.R.layout.simple_list_item_1, pode ser usado para mostrar listas simples, com apenas um texto descrevendo o item.
    • Neste aplicativo essa template é o suficiente pois iremos apenas mostrar o nome do Contato na lista.
  • Método onBindViewHolder
    • Esse método usa o valor da posição atual, position, na lista para identificar o contato correto a partir de uma lista de contatos armazenada no adaptador.
    • Os dados do contato encontrado são repassados a ViewHolder informando o nome do contato para exibir como item da lista ao mesmo tempo que se armazena o ID do contato no ViewHolder.
    • Observe que é necessário anexar o valor do ID do contato a ViewHolder pois a mesma usará esse valor para encaminhar a ação ao se clicar nesse contato específico.
  • Método setContacts
    • Esse método anexa uma nova lista de contatos ao adaptador e sinaliza para a lista ser redesenhada através do método notifyDataSetChanged.

5.3. Classe ItemDivider

A classe ItemDivider é utilizada no desenho da RecyclerView. Sua definição é simples e genérica. Ela cria um desenho de uma linha reta para separar os itens da lista.

public class ItemDivider extends RecyclerView.ItemDecoration{
private final Drawable divider;

// o construtor carrega a divisória de itens de lista interna
ItemDivider (Context context) {
int[] attrs = {android.R.attr.listDivider};
divider = context.obtainStyledAttributes(attrs).getDrawable(0);
}

// desenha as divisórias de itens de lista na RecylerView

@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
super.onDrawOver(c, parent, state);

// calcula as coordenadas x esquerda/direita de todas as divisórias
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();

// para todos os itens, menos o último, desenha uma linha abaixo dele
for (int i = 0; i < parent.getChildCount() - 1; i++) {
View item = parent.getChildAt(i);

// calcula as coodenadas y superior/inferior da divisória atual
int top = item.getBottom() + ((RecyclerView.LayoutParams)
item.getLayoutParams()).bottomMargin;
int bottom = top + divider.getIntrinsicHeight();

// desenha a divisória com os limites calculados
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
}

6. Finalizando a integração entre telas e exibindo dados fictícios

Para finalizar o aplicativo vamos configurar o fragmento inicial para exibir uma lista com alguns dados fictícios. Assim, será possível finalizar a transição entre as telas. Para tanto, modifique a classe MainFragment como a seguir.

package ...

import ...

public class MainFragment extends Fragment {

private MainViewModel mainViewModel;

// adaptor para ser uma usada com a lista de contatos
ContactsAdapter contactsAdapter;

...

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main_fragment, container, false);
// cria uma referência a recyclerview
RecyclerView recyclerView = view.findViewById(R.id.recyclerView);
// Configura arecycler view para exibir os itens na vertical
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getBaseContext()));
// anexa um ItemDecorator personalizado para desenhar divisórias entre os itens da lista
recyclerView.addItemDecoration(new ItemDivider(getContext()));
// cria um adaptador para preencher os dados da lista
// o adaptar tambem vincula uma ação para quando for clicado sobre um contato
contactsAdapter = new ContactsAdapter(new ContactsAdapter.ContactClickListener() {
@Override
public void onClick(int contactID) {
// Quando clicado sobre um contato, o método onContactSelected é invocado
onContactSelected(contactID);
}
});
recyclerView.setAdapter(contactsAdapter); // vincula o adaptador à lista
// cria uma referencia ao FAB e vincula uma ação
FloatingActionButton addButton = view.findViewById(R.id.addButton);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Quando for clicado no FAB o método onAddContact será invocado
onAddContact();
}
});
return view;
}

...

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
// TODO: Use the ViewModel

ArrayList<Contact> list = new ArrayList<Contact>();
list.add(new Contact(1,"Fulano", "1234", "a@a.com"));
list.add(new Contact(2,"Beltrano", "4567", "b@a.com"));
list.add(new Contact(3,"Ciclano", "8888", "c@a.com"));
contactsAdapter.setContacts(list);
}

}

As modificações incluem a inicialização e configuração do adaptador para a RecylerView e a adição três contatos fictícios no método onActivityCreated. Neste método, uma lista de contatos é criada e repassada ao adaptador através do método setContacts. Lembre que o método setContacts recebe uma lista de valores do tipo contato, configura seus elementos gráficos e solicita o redesenho da lista na tela. 

Agora, com a segunda parte do projeto podemos rodar o aplicativo e navegar entre as telas corretamente. Ainda não serão cadastrados nem exibidos os dados corretos de cada contato, mas podemos visualizar as telas e conferir o desenho da interface gráfica. Na próxima etapa do projeto vamos configurar o banco de dados para a Agenda de Contatos e implementar perfeitamente as funcionalidades de criar um novo contato, exibir os dados de um contato existente, editar um contato já existente e excluir um contato selecionado. 

Até mais a próxima etapa!