Fragmentos e o componente de navegação

No codelab Atividades e Intents, você adicionou intents ao app Words (link em inglês) para navegar entre duas atividades. Embora seja útil conhecer esse padrão de navegação, ele é apenas uma parte da criação de interfaces de usuário dinâmicas para seus apps. Muitos apps Android não precisam de uma atividade separada para cada tela. Na verdade, há muitos padrões comuns de IU, como guias, em uma única atividade, usando algo chamado fragmentos.

586ff7b88b0d2455.png

Um fragmento é uma parte reutilizável da IU que pode ser reutilizada e incorporada em uma ou mais atividades. Na captura de tela acima, o toque em uma guia não aciona uma intent para exibir a próxima tela. Em vez disso, a alternância entre guias simplesmente troca o fragmento atual por outro. Tudo isso acontece sem iniciar outra atividade.

Você pode até mesmo exibir vários fragmentos de uma vez em uma única tela, como um layout mestre/detalhe para tablets. No exemplo abaixo, tanto a IU de navegação à esquerda quanto o conteúdo à direita podem estar em um fragmento separado. Os dois fragmentos existem simultaneamente na mesma atividade.

92f1ecb9aadb7797.png

Como você pode ver, os fragmentos são uma parte essencial da criação de apps de alta qualidade. Neste codelab, você aprenderá as noções básicas sobre fragmentos e converterá o app Words para usá-los. Você também aprenderá a usar o componente de navegação do Jetpack e a trabalhar com um novo arquivo de recurso chamado gráfico de navegação para navegar entre fragmentos na mesma atividade do host. Ao final deste codelab, você terá as habilidades básicas para implementar fragmentos no seu próximo app.

Pré-requisitos

Para fazer este codelab, você já precisa saber:

  • adicionar arquivos XML de recursos e arquivos Kotlin a um projeto do Android Studio;
  • como o ciclo de vida da atividade funciona em um alto nível;
  • substituir e implementar métodos em uma classe existente;
  • criar instâncias de classes Kotlin, acessar propriedades de classes e chamar métodos;
  • o básico sobre valores anuláveis e não anuláveis e como processar valores nulos com segurança.

O que você aprenderá

  • A diferença entre o ciclo de vida do fragmento e da atividade.
  • Como converter uma atividade existente em um fragmento.
  • Como adicionar destinos a um gráfico de navegação e transmitir dados entre fragmentos usando o plug-in Safe Args.

O que você criará

  • Você modificará o app Words para usar uma única atividade e vários fragmentos e navegar entre eles usando o componente de navegação.

O que é necessário

  • Um computador com o Android Studio instalado.
  • O código da solução do app Words, do codelab Atividades e intents.

Neste codelab, você continuará de onde parou no fim do codelab Atividades e intents com o app Words. Se você já concluiu esse codelab, fique à vontade para usar seu código como ponto de partida. Você também pode fazer o download do código até este ponto no GitHub.

Fazer o download do código inicial para este codelab

Este codelab oferece um código inicial para você estender com os recursos ensinados. O código inicial pode conter um código que você já conheceu em codelabs anteriores. Ele também pode conter um código desconhecido e que você aprenderá em codelabs futuros.

Se você usar o código inicial do GitHub, o nome da pasta é android-basics-kotlin-words-app-activities. Selecione essa pasta ao abrir o projeto no Android Studio.

Para encontrar o código deste codelab e abri-lo no Android Studio, faça o seguinte.

Buscar o código

  1. Clique no URL fornecido. Isso abrirá a página do GitHub referente ao projeto em um navegador.
  2. Na página do GitHub do projeto, clique no botão Code, que exibirá uma caixa de diálogo.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. Na caixa de diálogo, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
  2. Localize o arquivo no computador, que provavelmente está na pasta Downloads.
  3. Clique duas vezes no arquivo ZIP para descompactá-lo. Isso criará uma nova pasta com os arquivos do projeto.

Abrir o projeto no Android Studio

  1. Inicie o Android Studio.
  2. Na janela Welcome to Android Studio, clique em Open an existing Android Studio project.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

Observação: caso o Android Studio já esteja aberto, selecione a opção File > New > Import Project.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Na caixa de diálogo Import Project, vá até a pasta do projeto descompactada, que provavelmente está na pasta Downloads.
  2. Clique duas vezes nessa pasta do projeto.
  3. Aguarde o Android Studio abrir o projeto.
  4. Clique no botão Run j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg para criar e executar o app. Confira se ele funciona da forma esperada.
  5. Procure os arquivos do projeto na janela de ferramentas Project para ver como o app foi implementado.

Um fragmento é simplesmente uma parte reutilizável da interface do usuário do app. Assim como as atividades, os fragmentos têm um ciclo de vida e podem responder à entrada do usuário. Um fragmento está sempre contido na hierarquia de visualização de uma atividade quando é exibido na tela. Devido à ênfase na capacidade de reutilização e modularidade, é possível que vários fragmentos sejam hospedados simultaneamente por uma única atividade. Cada fragmento gerencia o próprio ciclo de vida separado.

Ciclo de vida dos fragmentos

Assim como as atividades, os fragmentos podem ser inicializados e removidos da memória e, ao longo da existência deles, aparecer, desaparecer e reaparecer na tela. Além disso, como as atividades, os fragmentos têm um ciclo de vida com vários estados e oferecem diversos métodos que podem ser modificados para responder às transições entre eles. O ciclo de vida do fragmento tem cinco estados, representado pela enumeração Lifecycle.State.

  • INITIALIZED: uma nova instância do fragmento foi gerada.
  • CREATED: os primeiros métodos do ciclo de vida do fragmento foram chamados. Durante esse estado, a visualização associada ao fragmento também é criada.
  • STARTED: o fragmento está visível na tela, mas não tem "foco", o que significa que não pode responder à entrada do usuário.
  • RESUMED: o fragmento está visível e tem foco.
  • DESTROYED: o objeto do fragmento foi removido da instância.

Também semelhante às atividades, a classe Fragment oferece muitos métodos que você pode modificar para responder aos eventos do ciclo de vida.

  • onCreate(): o fragmento foi instanciado e está no estado CREATED. No entanto, a visualização correspondente ainda não foi criada.
  • onCreateView(): é neste método que você infla o layout. O fragmento entra no estado CREATED.
  • onViewCreated(): é chamado depois que a visualização é criada. Nesse método, você normalmente vincula visualizações específicas a propriedades chamando findViewById().
  • onStart(): o fragmento entra no estado STARTED.
  • onResume(): o fragmento entra no estado RESUMED e agora tem foco (pode responder à entrada do usuário).
  • onPause(): o fragmento entra novamente no estado STARTED. A IU está visível para o usuário.
  • onStop(): o fragmento entra novamente no estado CREATED. O objeto é instanciado, mas não é mais exibido na tela.
  • onDestroyView(): chamado logo antes do fragmento entrar no estado DESTROYED. A visualização já foi removida da memória, mas o objeto do fragmento ainda existe.
  • onDestroy(): o fragmento entra no estado DESTROYED.

O gráfico abaixo resume o ciclo de vida do fragmento e as transições entre os estados.

74470aacefa170bd.png

Os estados do ciclo de vida e os métodos de callback são muito parecidos com os usados em atividades. No entanto, há uma diferença no método onCreate(). Com as atividades, você precisa usar esse método para inflar o layout e vincular visualizações. No entanto, no ciclo de vida do fragmento, o onCreate() é chamado antes da criação da visualização. Portanto, não é possível inflar o layout nele. Logo, faça isso no onCreateView(). Em seguida, após a criação da visualização, o método onViewCreated() é chamado, no qual você pode vincular propriedades a visualizações específicas.

Ainda que pareça muito complicado, agora você tem as noções básicas de como os fragmentos funcionam e das semelhanças e diferenças deles em relação às atividades. Durante este codelab, você colocará esse conhecimento em prática. Primeiro, você migrará o app Words em que trabalhou anteriormente para usar um layout baseado em fragmentos. Depois, você implementará a navegação entre fragmentos em uma única atividade.

Assim como nas atividades, cada fragmento que você adicionar consistirá em dois arquivos: um arquivo XML para o layout e uma classe Kotlin para exibir dados e processar as interações do usuário. Você adicionará um fragmento à lista de letras e outro à lista de palavras.

  1. Com app selecionado no Project Navigator, adicione os seguintes fragmentos (File > New > Fragment > Fragment (Blank)). Um arquivo de classe e outro de layout serão gerados para cada fragmento.
  • No primeiro fragmento, defina o Fragment Name como LetterListFragment. O Fragment Layout Name precisa ser preenchido como fragment_letter_list.

898650e4cd0b2486.png

  • Para o segundo fragmento, defina o Fragment Name como WordListFragment. O Fragment Layout Name precisa ser preenchido como fragment_word_list.xml.

4f04fca641487da1.png

  1. As classes de Kotlin geradas para os dois fragmentos contêm uma grande quantidade de código boilerplate usado com frequência ao implementar fragmentos. No entanto, como você está aprendendo sobre fragmentos pela primeira vez, exclua todas as informações, exceto a declaração da classe para o LetterListFragment e o WordListFragment dos dois arquivos. Explicaremos como implementar os fragmentos do zero para que você saiba como todo o código funciona. Depois de excluir o código boilerplate, os arquivos Kotlin serão exibidos da seguinte forma.

LetterListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class LetterListFragment : Fragment() {

}

WordListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class WordListFragment : Fragment() {

}
  1. Copie o conteúdo do arquivo activity_main.xml no fragment_letter_list.xml e o conteúdo do activity_detail.xml no fragment_word_list.xml. Atualize tools:context no fragment_letter_list.xml para .LetterListFragment e tools:context no fragment_word_list.xml para .WordListFragment.

Após as mudanças, os arquivos de layout de fragmento ficarão assim.

fragment_letter_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".WordListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp" />

</FrameLayout>

fragment_word_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".WordListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp"
       tools:listitem="@layout/item_view" />

</FrameLayout>

Assim como acontece com as atividades, você precisa inflar o layout e vincular visualizações individuais. Há algumas pequenas diferenças ao trabalhar com o ciclo de vida do fragmento. Explicaremos o processo de configuração do LetterListFragment para que você possa fazer o mesmo para o WordListFragment.

Para implementar a vinculação de visualizações no LetterListFragment, primeiro é necessário conseguir uma referência anulável para a FragmentLetterListBinding. Classes Binding como essa são geradas pelo Android Studio para cada arquivo de layout quando a propriedade viewBinding é ativada na seção buildFeatures do arquivo build.gradle. Você só precisa atribuir propriedades na classe do fragmento para cada visualização na FragmentLetterListBinding.

O tipo precisa ser FragmentLetterListBinding? e ter um valor inicial de null. Por que torná-lo anulável? Porque não é possível inflar o layout até que a onCreateView() seja chamada. Há um período entre a criação da instância LetterListFragment (quando o ciclo de vida começa com o onCreate()) e quando essa propriedade pode ser realmente usada. Além disso, visualizações dos fragmentos podem ser criadas e destruídas várias vezes durante o ciclo de vida do fragmento. Por esse motivo, você também precisa redefinir o valor em outro método do ciclo de vida, o onDestroyView().

  1. No arquivo LetterListFragment.kt, comece acessando uma referência da FragmentLetterListBinding e nomeie-a como _binding.
private var _binding: FragmentLetterListBinding? = null

Como ela é anulável, sempre que você acessar uma propriedade de _binding (por exemplo, _binding?.someView), precisará incluir ? para ter proteção contra valores nulos. No entanto, isso não significa que você precisa poluir seu código com vários pontos de interrogação por causa de apenas um valor nulo. Se você tiver certeza de que um valor não será nulo ao acessá-lo, você poderá anexar !! ao nome do tipo. Em seguida, poderá acessá-lo como qualquer outra propriedade, sem o operador ?.

  1. Crie uma nova propriedade, chamada "binding" (sem o sublinhado), e defina-a como _binding!!.
private val binding get() = _binding!!

Aqui, get() significa que esta propriedade é "somente de acesso". Isso significa que você pode acessar o valor, mas, depois de atribuído (como acontece aqui), não poderá atribuí-lo a outra propriedade.

  1. Para implementar o onCreate(), basta chamar setHasOptionsMenu().
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setHasOptionsMenu(true)
}
  1. Em fragmentos, o layout é inflado no método onCreateView(). Implemente o onCreateView() inflando a visualização, definindo o valor de _binding e retornando a visualização raiz.
override fun onCreateView(
   inflater: LayoutInflater, container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentLetterListBinding.inflate(inflater, container, false)
   val view = binding.root
   return view
}
  1. Abaixo da propriedade binding, crie uma propriedade para a visualização de reciclagem.
private lateinit var recyclerView: RecyclerView
  1. Em seguida, defina o valor da propriedade recyclerView no onViewCreated() e chame o chooseLayout() como você fez na MainActivity. O método chooseLayout() será movido para o LetterListFragment em breve. Por isso, não se preocupe com o erro que será exibido.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   recyclerView = binding.recyclerView
   chooseLayout()
}

A classe de vinculação já criou uma propriedade para a recyclerView, e você não precisa chamar findViewById() para cada visualização.

  1. Por fim, na onDestroyView(), redefina a propriedade _binding para null, já que a visualização não existe mais.
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. O único outro ponto a ser observado é que existem algumas diferenças sutis com o método onCreateOptionsMenu() ao trabalhar com fragmentos. Embora a classe Activity tenha uma propriedade global chamada menuInflater, o fragmento não tem. O inflador do menu é transmitido para onCreateOptionsMenu(). Observe também que o método onCreateOptionsMenu() usado em fragmentos não exige uma instrução de retorno. Implemente o método como mostrado:
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
   inflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu.findItem(R.id.action_switch_layout)
   setIcon(layoutButton)
}
  1. Mova o código restante dos métodos chooseLayout(), setIcon() e onOptionsItemSelected() da MainActivity sem mudanças. A única diferença importante é que, ao contrário das atividades, um fragmento não é um Context. Não é possível transmiti-lo usando this (referindo-se ao objeto do fragmento) como o contexto do gerenciador de layout. No entanto, os fragmentos oferecem uma propriedade context que pode ser usada para isso. O restante do código é idêntico à MainActivity.
private fun chooseLayout() {
   when (isLinearLayoutManager) {
       true -> {
           recyclerView.layoutManager = LinearLayoutManager(context)
           recyclerView.adapter = LetterAdapter()
       }
       false -> {
           recyclerView.layoutManager = GridLayoutManager(context, 4)
           recyclerView.adapter = LetterAdapter()
       }
   }
}

private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_linear_layout)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           isLinearLayoutManager = !isLinearLayoutManager
           chooseLayout()
           setIcon(item)

           return true
       }

       else -> super.onOptionsItemSelected(item)
   }
}
  1. Por fim, copie a propriedade isLinearLayoutManager da MainActivity. Coloque-a abaixo da declaração da propriedade recyclerView.
private var isLinearLayoutManager = true
  1. Agora que toda a funcionalidade foi movida para o LetterListFragment, a classe MainActivity só precisa inflar o layout para que o fragmento seja exibido na visualização. Exclua tudo, exceto onCreate(), da MainActivity. Depois das mudanças, a MainActivity conterá somente o seguinte.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

Sua vez

Isso é tudo que basta para migrar a MainActivity para o LettersListFragment. A migração da DetailActivity é quase idêntica. Realize as etapas a seguir para migrar o código para o WordListFragment.

  1. Copie o objeto complementar da DetailActivity para o WordListFragment. Verifique se a referência a SEARCH_PREFIX no WordAdapter está atualizada para referenciar o WordListFragment.
  2. Adicione uma variável _binding. A variável precisa ser anulável e ter null como o valor inicial.
  3. Adicione uma variável apenas de acesso chamada "binding" igual à variável _binding.
  4. Infle o layout na onCreateView(), definindo o valor de _binding e retornando a visualização raiz.
  5. Faça todas as configurações restantes no onViewCreated(): acesse uma referência para a visualização de reciclagem, defina o gerenciador de layout e o adaptador e adicione a decoração do item. Você precisará acessar a letra da intent. Como os fragmentos não têm uma propriedade intent e normalmente não podem acessar a intent da atividade pai. Por enquanto, você usará a referência a activity.intent (em vez de intent na DetailActivity) para acessar os extras.
  6. Redefina _binding como nulo em onDestroyView.
  7. Exclua o código restante da DetailActivity, mantendo apenas o método onCreate().

Tente seguir as etapas por conta própria antes de continuar. Uma explicação detalhada está disponível na próxima etapa.

Esperamos que você tenha gostado de migrar a DetailActivity para o WordListFragment. Isso é quase idêntico à migração da MainActivity para o LetterListFragment. Se você não conseguiu acompanhar algum passo, as etapas estão resumidas a seguir.

  1. Primeiro, copie o objeto complementar para o WordListFragment.
companion object {
   val LETTER = "letter"
   val SEARCH_PREFIX = "https://www.google.com/search?q="
}
  1. Em seguida, no LetterAdapter, no onClickListener() em que você executa a intent, é preciso atualizar a chamada para putExtra(), substituindo a DetailActivity.LETTER pelo WordListFragment.LETTER.
intent.putExtra(WordListFragment.LETTER, holder.button.text.toString())
  1. Da mesma forma, no WordAdapter, você precisa atualizar o onClickListener() quando navegar para os resultados da pesquisa, substituindo DetailActivity.SEARCH_PREFIX por WordListFragment.SEARCH_PREFIX.
val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}")
  1. No WordListFragment, adicione uma variável binding do tipo FragmentWordListBinding?.
private var _binding: FragmentWordListBinding? = null
  1. Em seguida, crie uma variável somente de acesso para referenciar visualizações sem precisar usar ?.
private val binding get() = _binding!!
  1. Em seguida, infle o layout, atribuindo a variável _binding e retornando a visualização raiz. Para fragmentos, isso é feito na onCreateView(), não no onCreate().
override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentWordListBinding.inflate(inflater, container, false)
   return binding.root
}
  1. Em seguida, implemente o onViewCreated(). Isso é quase idêntico à configuração da recyclerView em onCreateView() na DetailActivity. No entanto, como os fragmentos não têm acesso direto à intent, é necessário referenciá-la com activity.intent. Você precisa fazer isso na onCreateView(). No entanto, não há garantia de que a atividade exista anteriormente no ciclo de vida.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   val recyclerView = binding.recyclerView
   recyclerView.layoutManager = LinearLayoutManager(requireContext())
   recyclerView.adapter = WordAdapter(activity?.intent?.extras?.getString(LETTER).toString(), requireContext())

   recyclerView.addItemDecoration(
       DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
   )
}
  1. Por fim, redefina a variável _binding no onDestroyView().
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. Com toda essa funcionalidade movida para o WordListFragment, agora você pode excluir o código da DetailActivity. Só o método onCreate() precisa ser mantido.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityDetailBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

Remover a DetailActivity

Agora que você migrou a funcionalidade da DetailActivity para o WordListFragment, a DetailActivity não é mais necessária. Você pode excluir a DetailActivity.kt e o arquivo activity_detail.xml e fazer uma pequena mudança no manifesto.

  1. Primeiro, exclua o DetailActivity.kt.

dd3b0bcf3ec81c9.png

  1. Verifique se a opção Safe Delete está desmarcada e clique em OK.

f2f1ff137b0057a7.png

  1. Em seguida, exclua o activity_detail.xml. Novamente, verifique se a opção Safe Delete está desmarcada.

6090c1d640433e07.png

  1. Por fim, como a DetailActivity não existe mais, remova o seguinte código do AndroidManifest.xml.
<activity
   android:name=".DetailActivity"
   android:parentActivityName=".MainActivity" />

Depois de excluir a atividade detalhada, você terá dois fragmentos (LetterListFragment e WordListFragment) e uma única atividade (MainActivity). Na próxima seção, você aprenderá mais sobre o componente de navegação do Jetpack e editará o activity_main.xml para que ele possa exibir e navegar entre fragmentos, em vez de ter um layout estático.

O Android Jetpack oferece o componente de navegação para ajudar você a gerenciar qualquer implementação de navegação, simples ou complexa, no seu app. O componente de navegação tem três partes principais que você usará para implementar a navegação no app Words.

  • Gráfico de navegação: é um arquivo XML que oferece uma representação visual da navegação do app. O arquivo consiste em destinos que correspondem a atividades e fragmentos individuais, além das ações entre eles, que podem ser usadas no código para navegar entre um destino e outro. Assim como acontece com os arquivos de layout, o Android Studio oferece um editor visual para adicionar destinos e ações ao gráfico de navegação.
  • NavHost: um NavHost é usado para exibir destinos de um gráfico de navegação em uma atividade. Quando você navega entre fragmentos, o destino mostrado no NavHost é atualizado. Você usará uma implementação integrada, chamada NavHostFragment, na sua MainActivity.
  • NavController: o objeto NavController permite que você controle a navegação entre os destinos exibidos no NavHost. Ao usar intents, era necessário chamar a startActivity para navegar para uma nova tela. Com o componente de navegação, é possível chamar o método navigate() do NavController para mudar o fragmento exibido. O NavController também ajuda a lidar com tarefas comuns, como responder ao botão "para cima" do sistema para voltar ao fragmento exibido anteriormente.
  1. No arquivo build.gradle do projeto, em buildscript > ext, abaixo de material_version, defina nav_version como 2.3.1.
buildscript {
    ext {
        appcompat_version = "1.2.0"
        constraintlayout_version = "2.0.2"
        core_ktx_version = "1.3.2"
        kotlin_version = "1.3.72"
        material_version = "1.2.1"
        nav_version = "2.3.1"
    }

    ...

}

  1. No arquivo build.gradle do app, adicione o seguinte ao grupo de dependências.
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Plug-in Safe Args

Ao implementar a navegação no app Words pela primeira vez, você usou uma intent explícita nas duas atividades. Para transmitir dados entre as duas atividades, você chamou o método putExtra(), transmitindo a letra selecionada.

Antes de começar a implementar o componente de navegação no app Words, você também adicionará algo chamado Safe Args, um plug-in do Gradle que ajudará a oferecer segurança de tipo ao transmitir dados entre fragmentos.

Realize as seguintes etapas para integrar o SafeArgs ao seu projeto.

  1. No arquivo build.gradle de nível superior, em buildscript > dependencies, adicione o caminho de classe a seguir.
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
  1. No arquivo build.gradle do nível do app, adicione androidx.navigation.safeargs.kotlin na parte superior de plugins.
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs.kotlin'
}
  1. Depois de editar os arquivos do Gradle, você verá um banner amarelo na parte superior solicitando a sincronização do projeto. Clique em "Sync Now" e aguarde um ou dois minutos enquanto o Gradle atualiza as dependências do projeto para refletir as mudanças.

854d44a6f7c4c080.png

Quando a sincronização for concluída, você poderá seguir para a próxima etapa, em que adicionará um gráfico de navegação.

Agora que você tem familiaridade com os fragmentos e o ciclo de vida deles, é hora de aprender coisas mais interessantes. A próxima etapa é incorporar o componente de navegação. O componente de navegação se refere ao conjunto de ferramentas para implementar a navegação, particularmente entre fragmentos. Você trabalhará com um novo editor visual para ajudar a implementar a navegação entre fragmentos: o gráfico de navegação (ou NavGraph).

O que é um gráfico de navegação?

O gráfico de navegação (ou NavGraph) é um mapeamento virtual da navegação do app. Cada tela, ou fragmento nesse caso, se torna um possível "destino" que você pode acessar. Um NavGraph pode ser representado por um arquivo XML que mostra como cada destino se relaciona com os outros.

Internamente, isso cria uma nova instância da classe NavGraph. No entanto, os destinos do gráfico de navegação são exibidos para o usuário pela FragmentContainerView. Tudo o que você precisa fazer é criar um arquivo XML e definir os destinos possíveis. Em seguida, use o código gerado para navegar entre fragmentos.

Usar uma FragmentContainerView na MainActivity

Como os layouts agora estão contidos no fragment_letter_list.xml e no fragment_word_list.xml, o arquivo activity_main.xml não precisa mais conter o layout da primeira tela do app. Em vez disso, você reutilizará a MainActivity para conter uma FragmentContainerView para atuar como o NavHost dos fragmentos. A partir desse momento, toda a navegação no app ocorrerá na FragmentContainerView.

  1. Substitua o conteúdo de FrameLayout no arquivo activity_main.xml, que é androidx.recyclerview.widget.RecyclerView com uma FragmentContainerView. Configure um ID de nav_host_fragment e defina a altura e a largura dele como match_parent para preencher todo o layout base.

Substitua isto:

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        ...
        android:padding="16dp" />

Por:

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/nav_host_fragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Abaixo do atributo ID, adicione um atributo name e defina-o como androidx.navigation.fragment.NavHostFragment. Embora você possa especificar um fragmento para esse atributo, ao configurá-lo como NavHostFragment, sua FragmentContainerView poderá navegar entre fragmentos.
android:name="androidx.navigation.fragment.NavHostFragment"
  1. Abaixo dos atributos layout_height e layout_width, adicione um atributo chamado app:navHost e defina-o como "true". Isso permitirá que o contêiner de fragmento interaja com a hierarquia de navegação. Por exemplo, se o botão "Voltar" do sistema for pressionado, o contêiner voltará ao fragmento exibido anteriormente, assim como acontece quando uma nova atividade é apresentada.
app:defaultNavHost="true"
  1. Adicione um atributo chamado app:navGraph e defina-o como "@navigation/nav_graph". Isso apontará para um arquivo XML que definirá como os fragmentos do app podem navegar entre eles. Por enquanto, o Android Studio mostrará um erro com o símbolo de não resolvido. Você resolverá isso na próxima etapa.
app:navGraph="@navigation/nav_graph"
  1. Por fim, como você adicionou dois atributos com o namespace do app, adicione o atributo xmlns:app ao FrameLayout.
<xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

Essas são todas as mudanças no arquivo activity_main.xml. Agora você criará o arquivo nav_graph.

Configurar o gráfico de navegação

Adicione um arquivo de gráfico de navegação (File > New > Android Resource File) e preencha os campos da seguinte forma.

  • File Name: nav_graph.xml., que é o mesmo nome definido para o atributo app:navGraph.
  • Resource Type: Navigation. O campo Directory Name será mudado automaticamente para "navigation". Uma nova pasta de recursos chamada "navigation" será criada.

e26ed91764a5616e.png

Ao criar o arquivo XML, você verá um novo editor visual. Como você já fez referência ao nav_graph na propriedade navGraph da FragmentContainerView, clique no novo botão no canto superior esquerdo da tela para adicionar um novo destino e criar um destino para cada fragmento (um para fragment_letter_list e outro para fragment_word_list).

307d036fce790feb.gif

Depois de serem adicionados, esses fragmentos serão exibidos no gráfico de navegação no meio da tela. Também é possível selecionar um destino específico usando a árvore de componentes à esquerda.

Criar uma ação de navegação

Para criar uma ação de navegação entre o letterListFragment e o wordListFragment, passe o cursor do mouse sobre o destino letterListFragment e arraste o círculo que aparece à direita para o destino wordListFragment.

c9477af5828a83f4.gif

Agora você verá uma seta criada para representar a ação entre os dois destinos. Clique na seta e você verá que essa ação tem um nome action_letterListFragment_to_wordListFragment no painel de atributos que pode ser referenciado no código.

Especificar argumentos para o WordListFragment

Ao navegar entre atividades usando uma intent, você especificou um "extra" para que a letra selecionada pudesse ser transmitida ao wordListFragment. O componente de navegação também permite transmitir parâmetros entre destinos e faz isso de maneira segura.

Selecione o destino do wordListFragment e, no painel de atributos, em Arguments, clique no botão de adição para criar um novo argumento.

O argumento precisa ter o nome letter e o tipo String. O plug-in Safe Args adicionado anteriormente é usado para isso. A especificação desse argumento como uma string garante que uma String seja esperada quando a ação de navegação for realizada no código.

b6bc3eaacd14bf50.png

Como configurar o destino inicial

Embora o NavGraph reconheça todos os destinos necessários, como a FragmentContainerView saberá qual fragmento será exibido primeiro? No NavGraph, você precisa definir a lista de letras como o destino inicial.

Para definir o destino inicial, selecione o letterListFragment e clique no botão Assign start destination.

99bb085e39dd7b4a.png

Isso é tudo o que você precisa fazer com o editor do NavGraph por enquanto. Agora, crie o projeto. Isso gerará um código baseado no seu gráfico de navegação para que a ação de navegação que acabou de criar possa ser usada.

Realizar a ação de navegação

Abra o arquivo LetterAdapter.kt para realizar a ação de navegação. São necessárias apenas duas etapas.

  1. Exclua o conteúdo do onClickListener() do botão. Em vez disso, você precisa recuperar a ação de navegação que acabou de criar. Adicione o seguinte ao onClickListener().
val action = LetterListFragmentDirections.actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString())

Você provavelmente não reconhece alguns desses nomes de classes e funções. Isso acontece porque eles foram gerados automaticamente depois da criação do projeto. O plug-in Safe Args adicionado na primeira etapa é usado para que as ações criadas no NavGraph sejam transformadas em código que você possa usar. Os nomes são bastante intuitivos. LetterListFragmentDirections permite que você consulte todos os caminhos de navegação possíveis do letterListFragment. A função actionLetterListFragmentToWordListFragment()

é a ação específica para navegar até o wordListFragment.

Assim que você tiver uma referência à ação de navegação, basta acessar uma referência do NavController (um objeto que permite executar ações de navegação) e chamar navigate() transmitindo a ação.

holder.view.findNavController().navigate(action)

Configurar a MainActivity

A última parte a ser configurada está na MainActivity. Poucas mudanças são necessárias na MainActivity para que todo o código funcione.

  1. Crie uma propriedade navController. Este item é marcado como lateinit porque será definido no onCreate.
private lateinit var navController: NavController
  1. Em seguida, depois de chamar setContentView() no onCreate(), acesse uma referência ao nav_host_fragment (o ID da FragmentContainerView) e atribua-o à sua propriedade navController.
val navHostFragment = supportFragmentManager
    .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
  1. Em seguida, no onCreate(), chame setupActionBarWithNavController(), transmitindo navController. Isso garante que os botões da barra de ações (barra de apps), como a opção de menu no LetterListFragment, sejam visíveis.
setupActionBarWithNavController(navController)
  1. Por fim, implemente onSupportNavigateUp(). Além de definir o defaultNavHost como true no XML, esse método permite que você processe o botão Para cima. No entanto, sua atividade precisa fornecer a implementação.
override fun onSupportNavigateUp(): Boolean {
   return navController.navigateUp() || super.onSupportNavigateUp()
}

Neste ponto, todos os componentes estarão preparados para iniciar a navegação com fragmentos. No entanto, agora que a navegação é realizada usando fragmentos em vez de intents, a intent extra para a letra que você usou no WordListFragment não funcionará mais. Na próxima etapa, você atualizará o WordListFragment para receber o argumento letter.

Anteriormente, você referenciava activity?.intent no WordListFragment para acessar o extra de letter. Embora funcione, essa não é uma prática recomendada, porque os fragmentos podem ser incorporados em outros layouts. Em apps maiores, é muito mais difícil presumir a qual atividade o fragmento pertence. Além disso, quando a navegação é realizada usando o nav_graph e argumentos seguros, não há intents. Portanto, tentar acessar os extras de intent simplesmente não funcionará.

Felizmente, acessar argumentos seguros é bem simples e você não precisa esperar até que o onViewCreated() seja chamado.

  1. No WordListFragment, crie uma propriedade letterId. Marque-a como lateinit para que você não precise torná-la anulável.
private lateinit var letterId: String
  1. Em seguida, substitua onCreate() (e não onCreateView() ou onViewCreated()) e adicione o seguinte.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    arguments?.let {
        letterId = it.getString(LETTER).toString()
    }
}

Como é possível que arguments sejam opcionais, observe que você chama let() e transmite um lambda. Esse código será executado se arguments não for nulo, transmitindo os argumentos não nulos para o parâmetro it. No entanto, se os arguments forem null, o lambda não será executado.

96a6a3253cea35b0.png

Embora não faça parte do código, o Android Studio oferece uma dica útil para que você saiba usar o parâmetro it.

O que é exatamente um Bundle? Pense nele como um par de chave-valor usado para transmitir dados entre classes, como atividades e fragmentos. Na verdade, você já usou um pacote quando chamou intent?.extras?.getString() ao executar uma intent na primeira versão deste app. Acessar a string de argumentos ao usar fragmentos funciona da mesma forma.

  1. Por fim, você pode acessar o letterId quando definir o adaptador da visualização de reciclagem. Substitua activity?.intent?.extras?.getString(LETTER).toString() no onViewCreated() pelo letterId.
recyclerView.adapter = WordAdapter(letterId, requireContext())

Parabéns! Execute o app. Agora ele pode navegar entre duas telas, sem usar intents e em uma única atividade.

Você converteu as duas telas para funcionarem com fragmentos. Antes de qualquer mudança ser feita, a barra de apps de cada fragmento tinha um título descritivo para cada atividade contida nela. No entanto, após a conversão para o uso de fragmentos, esse título está ausente da atividade detalhada.

c385595994ba91b5.png

Os fragmentos têm uma propriedade chamada "label", em que você pode definir o título que a atividade pai usará na barra de apps.

  1. No strings.xml, depois do nome do app, adicione a seguinte constante.
<string name="word_list_fragment_label">Words That Start With {letter}</string>
  1. Você pode definir o rótulo de cada fragmento no gráfico de navegação. Volte para o nav_graph.xml, selecione o letterListFragment na árvore de componentes e, no painel de atributos, defina o rótulo como a string app_name

4568d78c606999d.png

  1. Selecione o wordListFragment e defina o rótulo como word_list_fragment_label

7e7e55ea2dfb65bb.png

Parabéns por chegar até aqui! Execute o app mais uma vez e você verá ele como estava no início do codelab. Mas agora, toda a navegação estará hospedada em uma única atividade com um fragmento separado para cada tela.

O código da solução para este codelab está no projeto mostrado abaixo.

Para encontrar o código deste codelab e abri-lo no Android Studio, faça o seguinte.

Buscar o código

  1. Clique no URL fornecido. Isso abrirá a página do GitHub referente ao projeto em um navegador.
  2. Na página do GitHub do projeto, clique no botão Code, que exibirá uma caixa de diálogo.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. Na caixa de diálogo, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
  2. Localize o arquivo no computador, que provavelmente está na pasta Downloads.
  3. Clique duas vezes no arquivo ZIP para descompactá-lo. Isso criará uma nova pasta com os arquivos do projeto.

Abrir o projeto no Android Studio

  1. Inicie o Android Studio.
  2. Na janela Welcome to Android Studio, clique em Open an existing Android Studio project.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

Observação: caso o Android Studio já esteja aberto, selecione a opção File > New > Import Project.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Na caixa de diálogo Import Project, vá até a pasta do projeto descompactada, que provavelmente está na pasta Downloads.
  2. Clique duas vezes nessa pasta do projeto.
  3. Aguarde o Android Studio abrir o projeto.
  4. Clique no botão Run j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg para criar e executar o app. Confira se ele funciona da forma esperada.
  5. Procure os arquivos do projeto na janela de ferramentas Project para ver como o app foi implementado.
  • Fragmentos são partes reutilizáveis da IU que podem ser incorporados em atividades.
  • O ciclo de vida de um fragmento é diferente do ciclo de vida de uma atividade, com a configuração da visualização ocorrendo em onViewCreated(), em vez de onCreateView().
  • Uma FragmentContainerView é usada para incorporar fragmentos em outras atividades e pode gerenciar a navegação entre fragmentos.

Como usar o componente de navegação

  • Definir o atributo navGraph de uma FragmentContainerView permite que você navegue entre fragmentos de uma atividade.
  • O editor NavGraph permite adicionar ações de navegação e especificar argumentos entre destinos diferentes.
  • Embora a navegação com intents exija que você transmita extras, o componente de navegação usa o SafeArgs para gerar automaticamente classes e métodos para as ações de navegação, garantindo a segurança de tipo com argumentos.

Casos de uso de fragmentos

  • Usando o componente de navegação, muitos apps podem gerenciar todo o layout em uma única atividade, com toda a navegação ocorrendo em fragmentos.
  • Os fragmentos permitem padrões de layout comuns, como layouts mestre/detalhe em tablets ou várias guias dentro da mesma atividade.