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:navGraphaponta 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:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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
itemIdrecebido 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
itemIdcom 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
| Item | Como validar |
|---|---|
| Back stack | Home → Detail → Voltar retorna para Home |
| Up navigation | Seta Up no Detail volta para Home |
| Safe Args | Detail recebe itemId tipado (Long) sem Bundle manual |
| Deep link | Abrir myapp://detail/99 abre Detail com 99 |
| Animações | Transição ao entrar e ao voltar do Detail |
| Eventos do ViewModel | Clique em item navega uma vez, sem duplicar em rotação |