Navegação Android com Kotlin: Navigation Component e fluxo entre telas

Capítulo 6

Tempo estimado de leitura: 10 minutos

+ Exercício

O que é o Navigation Component e por que usar

O Navigation Component é um conjunto de bibliotecas do Jetpack que padroniza a navegação entre telas (principalmente Fragment) e centraliza o fluxo do app em um grafo de navegação (nav graph). Ele ajuda a:

  • Declarar destinos e ações de navegação em um único lugar (nav graph).
  • Gerenciar automaticamente o back stack (pilha de telas) e o botão Voltar.
  • Implementar Up navigation (seta de voltar na Toolbar) de forma consistente.
  • Passar parâmetros com segurança via Safe Args.
  • Configurar deep links básicos.
  • Aplicar animações de transição entre destinos.

Dependências essenciais (Navigation + Safe Args)

Adicione as dependências do Navigation no build.gradle do módulo (app). Use versões compatíveis com seu projeto.

dependencies {    implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")    implementation("androidx.navigation:navigation-ui-ktx:2.7.7")}

Para habilitar o Safe Args, aplique o plugin no Gradle do projeto e do módulo.

// build.gradle (Project)plugins {    id("androidx.navigation.safeargs.kotlin") version "2.7.7" apply false}
// build.gradle (Module: app)plugins {    id("androidx.navigation.safeargs.kotlin")}

NavHostFragment: o “container” onde os fragments aparecem

O NavHostFragment é o host que exibe os destinos do grafo. Em apps com navegação por fragments, normalmente ele fica no layout da Activity principal.

<androidx.fragment.app.FragmentContainerView    android:id="@+id/nav_host"    android:name="androidx.navigation.fragment.NavHostFragment"    android:layout_width="match_parent"    android:layout_height="match_parent"    app:defaultNavHost="true"    app:navGraph="@navigation/nav_graph" />
  • app:navGraph aponta para o arquivo do grafo.
  • app:defaultNavHost="true" faz o host interceptar o botão Voltar do sistema.

Criando o nav graph (destinos e ações)

Crie um arquivo em res/navigation/nav_graph.xml. Nele você declara:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Destinos: fragments que podem ser exibidos.
  • Ações: caminhos entre destinos (com opções como animações e comportamento do back stack).
  • Argumentos: parâmetros esperados por um destino.
  • Deep links: URLs/URIs que abrem diretamente um destino.

Exemplo de grafo com três telas: inicial (Home), detalhe (Detail) e configurações (Settings).

<navigation xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@+id/nav_graph"    app:startDestination="@id/homeFragment">    <fragment        android:id="@+id/homeFragment"        android:name="com.example.app.ui.home.HomeFragment"        android:label="Home">        <action            android:id="@+id/action_home_to_detail"            app:destination="@id/detailFragment" />        <action            android:id="@+id/action_home_to_settings"            app:destination="@id/settingsFragment" />    </fragment>    <fragment        android:id="@+id/detailFragment"        android:name="com.example.app.ui.detail.DetailFragment"        android:label="Detail">        <argument            android:name="itemId"            app:argType="long" />        <deepLink            app:uri="myapp://detail/{itemId}" />    </fragment>    <fragment        android:id="@+id/settingsFragment"        android:name="com.example.app.ui.settings.SettingsFragment"        android:label="Settings" /></navigation>

Entendendo back stack

Quando você navega de Home para Detail, o destino anterior fica no back stack. Ao pressionar Voltar, o Navigation retorna ao destino anterior automaticamente.

Up navigation (seta na Toolbar)

O botão Up (seta) normalmente deve ter o mesmo comportamento do Voltar dentro do app. Para integrar com a Toolbar/ActionBar, configure na Activity:

class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        val navController = findNavController(R.id.nav_host)        setupActionBarWithNavController(navController)    }    override fun onSupportNavigateUp(): Boolean {        val navController = findNavController(R.id.nav_host)        return navController.navigateUp() || super.onSupportNavigateUp()    }}

Se você usa uma Toolbar customizada, pode usar NavigationUI.setupWithNavController(toolbar, navController).

Navegando na prática: findNavController e ações

Dentro de um Fragment, você navega chamando findNavController(). Exemplo (sem Safe Args ainda):

binding.btnSettings.setOnClickListener {    findNavController().navigate(R.id.action_home_to_settings)}

Para abrir o detalhe com argumento, você poderia usar um Bundle, mas o recomendado é Safe Args (próxima seção).

Passagem segura de parâmetros com Safe Args

Com Safe Args, o Gradle gera classes de direção (Directions) e classes de argumentos (Args), evitando erros comuns como nome de chave errado ou tipo incorreto.

1) Declarar argumentos no destino

No nav graph, já declaramos:

<argument    android:name="itemId"    app:argType="long" />

2) Navegar enviando o argumento

No HomeFragment, use a classe gerada HomeFragmentDirections:

val action = HomeFragmentDirections.actionHomeToDetail(itemId = 42L)findNavController().navigate(action)

3) Ler o argumento no destino

No DetailFragment, use by navArgs():

class DetailFragment : Fragment(R.layout.fragment_detail) {    private val args: DetailFragmentArgs by navArgs()    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        val id = args.itemId        // usar id para carregar dados    }}

Deep links básicos para abrir uma tela específica

Deep links permitem abrir um destino do grafo via URI. No exemplo, o DetailFragment aceita:

myapp://detail/{itemId}

Para testar rapidamente, você pode disparar um intent (por exemplo, via ADB) apontando para essa URI. Quando o app recebe o deep link, o Navigation resolve o destino e preenche o argumento itemId.

Se você quiser que o deep link seja acessível externamente, normalmente também é necessário configurar intent-filter no Manifest para a Activity host. Um exemplo mínimo:

<activity android:name=".MainActivity">    <intent-filter>        <action android:name="android.intent.action.VIEW" />        <category android:name="android.intent.category.DEFAULT" />        <category android:name="android.intent.category.BROWSABLE" />        <data android:scheme="myapp" android:host="detail" />    </intent-filter></activity>

Observação: para deep links HTTP/HTTPS (App Links), há validações adicionais, mas aqui o foco é o básico.

Controlando animações de navegação

Você pode definir animações por ação no nav graph. Isso mantém o comportamento consistente e evita duplicação no código.

<action    android:id="@+id/action_home_to_detail"    app:destination="@id/detailFragment"    app:enterAnim="@anim/slide_in_right"    app:exitAnim="@anim/slide_out_left"    app:popEnterAnim="@anim/slide_in_left"    app:popExitAnim="@anim/slide_out_right" />

Crie os arquivos em res/anim. Exemplo simples:

<set xmlns:android="http://schemas.android.com/apk/res/android">    <translate        android:fromXDelta="100%"        android:toXDelta="0%"        android:duration="250" /></set>

Você também pode usar animações do tipo animator (property animations) em res/animator, dependendo do efeito desejado.

Back stack avançado: popUpTo e evitar telas duplicadas

Alguns fluxos pedem controle do back stack, por exemplo: após login, você não quer voltar para a tela de login. Para isso, use popUpTo e popUpToInclusive na ação.

<action    android:id="@+id/action_login_to_home"    app:destination="@id/homeFragment"    app:popUpTo="@id/loginFragment"    app:popUpToInclusive="true" />

Outro caso comum é evitar múltiplas instâncias da mesma tela ao clicar repetidamente. Uma opção é usar launchSingleTop ao navegar:

findNavController().navigate(    R.id.settingsFragment,    null,    navOptions { launchSingleTop = true })

Navegação disparada pelo ViewModel (sem múltiplos disparos)

Em apps com arquitetura reativa, é comum o ViewModel emitir eventos de navegação (ex.: após salvar, ir para outra tela). O cuidado principal é evitar que o evento seja reexecutado em recriações (rotação) ou em re-observações, causando navegação duplicada.

Padrão recomendado: eventos com Channel + Flow

Use um Channel para eventos de uso único e exponha como Flow. O Fragment coleta com repeatOnLifecycle.

// ViewModelclass HomeViewModel : ViewModel() {    sealed class NavEvent {        data class ToDetail(val itemId: Long) : NavEvent()        data object ToSettings : NavEvent()    }    private val _navEvents = Channel<NavEvent>(capacity = Channel.BUFFERED)    val navEvents = _navEvents.receiveAsFlow()    fun onItemClicked(id: Long) {        _navEvents.trySend(NavEvent.ToDetail(id))    }    fun onSettingsClicked() {        _navEvents.trySend(NavEvent.ToSettings)    }}
// Fragmentclass HomeFragment : Fragment(R.layout.fragment_home) {    private val vm: HomeViewModel by viewModels()    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        viewLifecycleOwner.lifecycleScope.launch {            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {                vm.navEvents.collect { event ->                    when (event) {                        is HomeViewModel.NavEvent.ToDetail -> {                            val action = HomeFragmentDirections                                .actionHomeToDetail(itemId = event.itemId)                            findNavController().navigate(action)                        }                        HomeViewModel.NavEvent.ToSettings -> {                            findNavController().navigate(                                HomeFragmentDirections.actionHomeToSettings()                            )                        }                    }                }            }        }    }}

Vantagens: o evento é consumido uma vez; não “re-dispara” ao reanexar observers como pode acontecer com estados persistentes.

Alternativa: SharedFlow configurado para eventos

Se preferir MutableSharedFlow, configure sem replay e com buffer:

private val _navEvents = MutableSharedFlow<NavEvent>(    replay = 0,    extraBufferCapacity = 1)val navEvents = _navEvents.asSharedFlow()

Emita com tryEmit ou emit em coroutine.

Exercício: criar um fluxo completo (Home → Detail → Settings)

Objetivo

Implementar um app com três fragments:

  • HomeFragment: lista simples (pode ser estática) com itens e um botão de configurações.
  • DetailFragment: mostra o itemId recebido e um botão para abrir configurações.
  • SettingsFragment: tela de configurações com um botão “Voltar”.

Requisitos do exercício:

  • Navegação via Navigation Component com nav graph.
  • Passagem de parâmetro itemId com Safe Args.
  • Deep link para o detalhe: myapp://detail/{itemId}.
  • Animação de transição ao abrir o detalhe.
  • Eventos de navegação disparados pelo ViewModel na Home.

Passo 1: criar o nav graph

Crie res/navigation/nav_graph.xml com os 3 destinos, ações e argumento itemId no detalhe (como mostrado anteriormente). Adicione também o deep link no DetailFragment.

Passo 2: configurar o NavHostFragment na Activity

No layout da Activity, adicione o FragmentContainerView com NavHostFragment e aponte para o nav graph. Na Activity, configure setupActionBarWithNavController e onSupportNavigateUp.

Passo 3: implementar HomeFragment com ViewModel emitindo eventos

Na Home, simule uma lista de itens com botões (ou uma lista simples). Ao clicar em um item, chame vm.onItemClicked(id). Ao clicar em configurações, chame vm.onSettingsClicked(). Colete navEvents e navegue com Safe Args.

// Exemplo de clique em item (pode ser em um botão)binding.btnItem42.setOnClickListener {    vm.onItemClicked(42L)}

Passo 4: DetailFragment lendo argumento e navegando

No detalhe, leia args.itemId e exiba na UI. Adicione um botão para abrir configurações.

private val args: DetailFragmentArgs by navArgs()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {    binding.txtId.text = "Item: ${args.itemId}"    binding.btnSettings.setOnClickListener {        findNavController().navigate(R.id.settingsFragment)    }}

Se quiser manter o uso de ações do grafo também aqui, crie uma ação action_detail_to_settings no nav graph e navegue por ela.

Passo 5: SettingsFragment e comportamento de voltar

Em configurações, o botão “Voltar” pode chamar:

binding.btnBack.setOnClickListener {    findNavController().navigateUp()}

Teste também o botão Voltar do sistema e a seta Up na Toolbar (se estiver configurada).

Passo 6: adicionar animações na ação Home → Detail

Crie os arquivos em res/anim e referencie-os na ação action_home_to_detail com enterAnim, exitAnim, popEnterAnim, popExitAnim.

Passo 7: testar o deep link

Garanta que o destino DetailFragment tem o deep link no nav graph. Se adicionou intent-filter na Activity, teste abrindo a URI myapp://detail/42 e verifique se o app abre diretamente no detalhe com itemId = 42.

Checklist de validação

ItemComo validar
Back stackHome → Detail → Voltar retorna para Home
Up navigationSeta Up no Detail volta para Home
Safe ArgsDetail recebe itemId tipado (Long) sem Bundle manual
Deep linkAbrir myapp://detail/99 abre Detail com 99
AnimaçõesTransição ao entrar e ao voltar do Detail
Eventos do ViewModelClique em item navega uma vez, sem duplicar em rotação

Agora responda o exercício sobre o conteúdo:

Ao usar Safe Args para navegar do HomeFragment para o DetailFragment passando um itemId, qual prática garante que o argumento seja enviado e lido de forma tipada, reduzindo erros de chave e tipo?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Safe Args gera classes Directions e Args, permitindo enviar e receber argumentos com tipos definidos (ex.: Long) via action... e by navArgs(), evitando erros comuns de chave e tipo.

Próximo capitúlo

Ciclo de vida Android: atividades, fragments e observação segura de estado

Arrow Right Icon
Capa do Ebook gratuito Android para Iniciantes com Kotlin: construindo seu primeiro app moderno
40%

Android para Iniciantes com Kotlin: construindo seu primeiro app moderno

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.