Android Jetpack — Navegação

Photo by Joseph Barrientos on Unsplash

Entrando no assunto Jetpack, e hoje o tema será Navegação, uma das libs mais problemáticas e interessantes do Jetpack. Problemática porque ela exige um pouco de paciência para conseguir fazer rodar (e se você quiser usar tem que estar no Android Studio Preview, ou Canary, que é a versão com features ainda em teste do AS), mesmo usando o AS Canary mais recente. É claro que deve-se dar um certo desconto, a lib ainda tá em alfa… Ela é interessante porque novamente o Google vem nos propor uma arquitetura de desenvolvimento de apps, dessa vez uma Activity e múltiplos Fragments.

Quem já desenvolve para Android (nem precisa ser por muito tempo) já sabe que Fragments são uma criatura estranha do ecossistema. São um componente de UI, mas não são um contexto (como as Activities), entretanto eles tem um lifecycle próprio que juntamente com o da Activity vira uma bagunça entranhada:

E tudo isso é muito lindo e importantede saber, mas cria um overhead no desenvolvimento que a lib de Navegação vem tentar simplificar.

A primeira vista a lib de Navegação desperta o pensamento: “Olha, um Storyboard pra Android”. Entretanto engana-se quem pessa isso já que a proposta é completamente outra.

Navigation é um Arch Component (tal qual ViewModel, LifeCycle e etc) que ajuda a simplificar a navegação entre “destinos” na sua aplicação. Isso que dizer que utilizando esse componente, pode-se ligar telas de forma que a navegação entre elas vai ser gerenciada pelo componente.

O app de exemplo que será utilizado é um que também usa outro Arch Component (Room) e é interessante para visualizar a integração entre as duas libs . O código completo pode ser encontrado aqui.

Como podem ver, uma aplicação muito comum. Foi utilizada uma BottomNavigationView para administrar as várias tabs da app e para cada tab existe um Fragment diferente.

Anteriormente, a administração da troca de tabs era responsabilidade do desenvolvedor. Era necessário criar um mecanismo que reagisse aos clicks das tabs e trocasse os Fragments de acordo, ou seja, ficar rodando um código parecido com esse:

E geralmente, para evitar tendo que adminstrar a BackStack quando tinha a necessidade de abrir uma outra tela a partir de um desses Fragments o desenvolvedor simplesmente abria uma nova Activity e dava bye bye para o BottomNavigationView. Inclusive o Facebook ainda faz isso…

Usando a lib

Primeiro, como toda e qualquer biblioteca, é preciso importá-la no arquivo build.gradle:

Será necessário uma Activity pronta para ser a NavHost, ou seja, ela que vai abrigar o fluxo que será definido no grafo de navegação mais na frente. Para definir uma Activity como NavHost é preciso adicionar ao xml dela essa indicação:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
    <fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
    <android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>

É bem simples, temos o BottomNavigationView e uma tag fragment onde define-se o name como NavHostFragment e o navGraph é apontado para o grafo de navegação que será criado no próximo passo.

Para criar o grafo de navegação basta clicar com o botão direito em cima da pasta de resources (res) e selecionar adicionar novo recurso xml. No pop up que vai aparecer preencha o nome do recurso nav_graph (ou outro nome que você prefereir) e selecione Navigation no tipo de recurso:

Isso irá criar um novo subdiretório de recursos e irá adicionar o novo xml que pode ser visualizado no editor de navegação. Para abrir o editor basta clicar para abrir o arquivo e selecionar Design na tab inferior, do mesmo modo quando vamos editar um xml de layout.

O editor tem 3 áreas, Destinations, a área de edição e a de atributos:

A sua Activity NavHost deve aparecer na área de destinations. Você pode adicionar Fragments existentes no seu projeto como destinations ou criar novos. As ligações entre Fragments são chamadas Actions . Nesse exemplo utilizou-se 5 Fragments que representam as tabs, bem como Fragments adicionais representando outras telas que podem estar ligadas a cada tab.

Os comportamentos dos Fragments são escritos em kotlin normalmente, mas a instanciação deles fica por conta da lib.

Depois de definidos tudo isso, basta apenas fazer a ligação entre as tabs do BottomNavigationView e os seus respectivos Fragments. Sempre que define-se um BottomNavigationView, é definido também um arquivo xml de menu que guiará as tabs dele, nesse exemplo ele é assim:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>

Para fazer a ligação entre os dois basta que os ids definidos para cada Fragment no editor de grafo sejam iguais aos definidos acima:

No exemplo o id para o Fragment destination home é navigation_home, ou seja o mesmo que o definido acima no arquivo de menu do BottomNavigationView. Finalmente, utilizamos uma linha de kotlin para finalizar com a ligação:

E simples assim o BottomNavigationView vai funcionar sem a necessidade de ficarmos gerenciando as transactions (embora seja possível adicionar animações via xml nas transições através do editor de grafos).

Outra parte importante do editor de grafos são as actions, ou as trocas de Fragments. Na tela abaixo por exemplo, pode-se associar o clique no FAB à troca de Fragment de listagem e o de adicionar item:

Tela bugada :) Mas dá pra entender a ideia

Quando uma Action é definida de uma “destination” a outra, pode-se adicionar várias funcionalidades a essa action tais como animações, argumentos que serão passados, comportamento ao receber o click to Back Button e opções de como será iniciado esse Fragment:

Um dos atributos mais importantes da action é o ID e geralmente o próprio AS o preenche automaticamente. Para utilizar o FAB para invocar essa transição (action) existem dois métodos:

Pode-se invocar a action diretamente utilizando o método navigate() que tem como parâmetro o id da action ou o id do Fragment de destino. E no caso de botões pode-se utilizar o método createNavigateOnClickListener() que também recebe o ID da action ou o ID do Fragment de destino e já retorna um ClickListener.