Se você desenvolve apps Android com Compose, 2026 trouxe uma mudança importante: o Navigation 3 saiu do campo de curiosidade para virar uma opção real de arquitetura. A proposta é simples de entender, mas poderosa na prática: tratar navegação como estado da UI, com back stack tipada, integração melhor com Compose e APIs mais modernas para transições, overlays e predictive back.
Neste guia, você vai entender o que é o Navigation 3, quando ele faz sentido, como configurar o projeto e como montar um fluxo de navegação idiomático em Kotlin. A ideia aqui não é hype. É mostrar onde essa biblioteca realmente ajuda, especialmente em apps novos ou em times que já abraçaram Compose por completo.
Se você ainda está consolidando a base do ecossistema Android, vale revisar também nosso guia de Jetpack Compose, o tutorial de Compose para iniciantes e o conteúdo sobre Kotlin para Android.
O que é o Navigation 3?
O Navigation 3 é a nova biblioteca de navegação do AndroidX pensada com uma abordagem Compose-first. Em vez de herdar a mentalidade do mundo de Fragments, ela parte da ideia de que telas, overlays e fluxo de ida e volta devem ser representados como estado serializável.
Na prática, isso significa algumas vantagens importantes:
- chaves de navegação tipadas;
- back stack controlada diretamente pelo seu estado;
- integração natural com Compose;
- suporte melhor para cenários modernos, como predictive back;
- base mais limpa para animações, overlays e resultados entre destinos.
Se você já comparou paradigmas de UI moderna em artigos como Jetpack Compose vs XML, vai perceber que o Navigation 3 segue a mesma filosofia: menos acoplamento com infra antiga e mais foco em estado declarativo.
Por que o Navigation 3 importa em 2026?
Durante muito tempo, muita gente usou o Navigation Compose tradicional e resolveu o problema. E tudo bem: ele continua útil. O ponto é que o Navigation 3 tenta corrigir algumas dores recorrentes em apps totalmente declarativos:
- Back stack mais explícita — você manipula uma estrutura de navegação clara, em vez de depender de abstrações mais opacas.
- Melhor alinhamento com serialização — as chaves são tipos reais, não apenas strings e rotas improvisadas.
- Mais controle para experiências modernas — overlays, resultados entre telas e transições ficam mais naturais.
- Arquitetura mais previsível — navegação vira parte do estado da aplicação, o que combina com MVI, MVVM e fluxos reativos.
Se o seu time já trabalha com coroutines, Flow e telas orientadas a estado, a curva de adoção é muito mais tranquila.
Dependências do projeto
Um setup inicial pode começar assim:
// build.gradle.kts
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.serialization")
}
android {
namespace = "br.dev.exemplo"
compileSdk = 36
defaultConfig {
applicationId = "br.dev.exemplo"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation("androidx.navigation3:navigation3-runtime:1.1.1")
implementation("androidx.navigation3:navigation3-ui:1.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
}
A parte do kotlin("plugin.serialization") é importante porque as chaves de navegação normalmente são serializáveis. Se você ainda não domina esse recurso, vale ler nosso glossário sobre serialization e o post sobre Kotlin Multiplatform, já que a modelagem serializável aparece em vários contextos modernos do ecossistema.
Modelando destinos tipados
Uma das ideias mais legais do Navigation 3 é parar de navegar por strings mágicas. Em vez disso, você define tipos:
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
data class DetalheArtigo(val slug: String)
@Serializable
object Favoritos
@Serializable
data class Perfil(val userId: String)
Isso já melhora a manutenção do projeto. Quando você muda um destino, o compilador passa a ajudar. É o mesmo ganho de segurança que a gente vê quando troca lógica frágil por sealed classes ou adota null safety.
Primeiro fluxo com rememberNavBackStack
Agora vamos montar uma navegação simples entre home e detalhe:
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import androidx.navigation3.ui.entryProvider
import androidx.navigation3.ui.entry
@Composable
fun AppNavigation() {
val backStack = rememberNavBackStack(Home)
NavDisplay(
backStack = backStack,
entryProvider = entryProvider {
entry<Home> {
HomeScreen(
onAbrirArtigo = { slug ->
backStack.add(DetalheArtigo(slug))
},
onAbrirFavoritos = {
backStack.add(Favoritos)
}
)
}
entry<DetalheArtigo> { destino ->
DetalheArtigoScreen(
slug = destino.slug,
onVoltar = { backStack.removeLastOrNull() }
)
}
entry<Favoritos> {
FavoritosScreen(
onVoltar = { backStack.removeLastOrNull() }
)
}
}
)
}
Perceba o padrão: a sua pilha é um estado Compose, e a UI renderiza conforme esse estado. Isso conversa muito bem com arquiteturas que já usam MVVM ou fluxos baseados em reducer.
Exemplo de telas reais
Vamos imaginar um app de conteúdo técnico em Kotlin:
@Composable
fun HomeScreen(
onAbrirArtigo: (String) -> Unit,
onAbrirFavoritos: () -> Unit,
) {
Scaffold(
topBar = {
TopAppBar(title = { Text("Kotlin Brasil App") })
}
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(onClick = { onAbrirArtigo("navigation-3-compose-android-2026") }) {
Text("Abrir artigo sobre Navigation 3")
}
OutlinedButton(onClick = onAbrirFavoritos) {
Text("Ver favoritos")
}
}
}
}
@Composable
fun DetalheArtigoScreen(
slug: String,
onVoltar: () -> Unit,
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Detalhe do artigo") },
navigationIcon = {
TextButton(onClick = onVoltar) {
Text("Voltar")
}
}
)
}
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(24.dp)
) {
Text("Slug recebido: $slug")
}
}
}
Essa abordagem evita muito boilerplate. E como a chave DetalheArtigo já carrega o slug, você reduz o risco de navegar com argumentos inconsistentes.
Quando o Navigation 3 é melhor que o Navigation Compose tradicional?
A resposta honesta é: depende do momento do projeto.
Vale mais a pena quando:
- o app é 100% Compose;
- o time quer navegação realmente orientada a estado;
- há preocupação com tipagem forte e serialização;
- o roadmap inclui transições mais sofisticadas, overlays ou predictive back;
- o projeto ainda não carregou legado forte de Fragments.
Talvez não valha migrar agora quando:
- o app atual já está estável com Navigation Compose tradicional;
- a equipe tem pouco tempo para revisar arquitetura;
- a navegação do produto é simples e não sofre dores reais;
- grande parte do fluxo ainda depende de Fragment ou XML.
Esse tipo de análise é parecido com a discussão de Kotlin vs Java em 2026: a melhor tecnologia não existe no vácuo, e sim dentro do custo de adoção do seu time.
Overlays, resultados e experiências modernas
Um dos pontos que mais chamam atenção no Navigation 3 é a evolução para cenas e overlays. Isso abre espaço para lidar melhor com dialogs, folhas modais e fluxos que devolvem resultado para a tela anterior.
Imagine uma tela de perfil que abre um seletor de tema como overlay. Em vez de improvisar estados espalhados pela UI, você consegue tratar isso como parte da navegação.
Um modelo simplificado seria algo assim:
@Serializable
data class EditorPerfil(val userId: String)
@Serializable
object SeletorTema
Com isso, você consegue representar na pilha algo como:
HomePerfil("42")SeletorTema
Essa clareza ajuda muito em debug, analytics e restauração de estado. Se você já se interessa por instrumentação, aproveite também nosso conteúdo sobre observabilidade em Kotlin e Tracy para observabilidade de IA, porque navegação bem modelada também melhora rastreabilidade de eventos.
Predictive back e UX Android moderna
O Android moderno está cada vez mais orientado a gestos, e o predictive back ganhou relevância real em apps que se preocupam com experiência refinada. O Navigation 3 conversa melhor com esse cenário porque sua pilha de navegação é explícita.
Na prática, isso traz benefícios como:
- comportamento de retorno mais previsível;
- integração mais clara com o ciclo de navegação;
- menos gambiarra para sincronizar estado de tela e gesto do sistema.
Esse detalhe importa bastante em apps com múltiplos níveis de profundidade, filtros, painéis laterais e telas com overlays.
Boas práticas para adotar Navigation 3
Se você resolver experimentar em produção, siga estas recomendações:
1. Comece por um fluxo isolado
Não tente reescrever o app inteiro de uma vez. Pilote numa feature nova, como onboarding, área autenticada ou detalhes de conteúdo.
2. Modele destinos com tipos pequenos
Evite colocar objetos gigantes na chave. Prefira IDs, slugs ou parâmetros mínimos.
3. Deixe a regra de negócio fora da navegação
Navegação deve descrever fluxo de tela, não carregar lógica de domínio. Para isso, use ViewModels, casos de uso e camadas que você já conhece de Clean Architecture em Kotlin.
4. Teste a serialização
Se a chave for serializável, valide cenários de restauração de processo e retomada de estado.
5. Observe o custo de migração
Em apps legados, às vezes o maior ganho está em melhorar a arquitetura ao redor primeiro, e só depois trocar a navegação.
Exemplo de integração com ViewModel
Um padrão comum é deixar o ViewModel emitir eventos que alteram a pilha:
class HomeViewModel : ViewModel() {
private val _eventos = MutableSharedFlow<HomeEvent>()
val eventos = _eventos.asSharedFlow()
fun abrirArtigo(slug: String) {
viewModelScope.launch {
_eventos.emit(HomeEvent.AbrirArtigo(slug))
}
}
}
sealed interface HomeEvent {
data class AbrirArtigo(val slug: String) : HomeEvent
}
E na tela:
LaunchedEffect(Unit) {
viewModel.eventos.collect { evento ->
when (evento) {
is HomeEvent.AbrirArtigo -> backStack.add(DetalheArtigo(evento.slug))
}
}
}
Esse estilo mantém a UI declarativa e ainda preserva uma fronteira clara entre intenção do usuário e mudança de navegação.
Vale a pena estudar Navigation 3 agora?
Sim, especialmente se você trabalha com Android moderno, Compose e arquitetura orientada a estado. Mesmo que seu time ainda não migre imediatamente, entender o Navigation 3 ajuda a enxergar melhor para onde o ecossistema Jetpack está caminhando.
Para quem está começando um app novo em 2026, ele já é uma alternativa muito plausível. Para apps maduros, a adoção precisa ser estratégica, mas faz sentido acompanhar desde já.
No fim, o maior valor do Navigation 3 não é apenas “navegar entre telas”. É permitir que a navegação deixe de ser uma camada especial e passe a ser tratada como parte legítima do estado da sua interface.
Perguntas Frequentes
Navigation 3 substitui automaticamente o Navigation Compose tradicional?
Não. As duas abordagens podem coexistir no ecossistema. O Navigation 3 é uma biblioteca nova, mais alinhada com Compose-first e modelagem por estado, mas a decisão de migração depende do contexto do projeto.
Preciso usar serialização com Navigation 3?
Na maioria dos exemplos modernos, sim. O padrão mais comum é modelar destinos com tipos serializáveis, o que melhora restauração de estado e segurança de navegação.
Navigation 3 serve para apps com Fragment?
O foco principal é Compose. Em apps fortemente baseados em Fragment, a adoção tende a fazer menos sentido, principalmente se a base atual já estiver estável.
Vale usar Navigation 3 em app novo?
Para apps novos, 100% Compose e com equipe confortável com arquitetura declarativa, sim. É justamente onde a biblioteca tende a entregar o maior benefício.
Como estudar mais navegação e arquitetura Android com Kotlin?
Além da documentação oficial, vale aprofundar em Jetpack Compose, MVVM com Kotlin, coroutines e Flow, porque esses conceitos se conectam diretamente com o modelo do Navigation 3. Para quem também desenvolve apps multiplataforma, é interessante comparar como a navegação funciona em outras stacks — como Rust com frameworks como Dioxus aborda UI declarativa e navegação tipada.