NavController é o componente central da biblioteca Navigation Compose que gerencia a pilha de navegação e o estado do back stack em aplicativos Android. Através do NavController são realizadas transições entre telas, retorno a páginas anteriores e transferência de dados entre rotas. De acordo com Android Developers (2025), NavController é um elemento obrigatório de qualquer aplicativo Compose com mais de uma tela. O controlador é criado via rememberNavController(), passado para NavHost e está disponível para chamar navigate() de qualquer ponto da composição. O suporte integrado do SavedStateHandle salva automaticamente o estado do ViewModel durante a reconfiguração.
Pontos Principais
NavController é uma classe da biblioteca Navigation Compose que implementa o controlador de navegação para aplicativos Compose. NavController gerencia a pilha NavBackStackEntry, onde cada entrada contém a rota, argumentos e estado da tela. O controlador suporta operações básicas de navegação: transição, retorno, substituição e limpeza.
Ao contrário do sistema View, onde a navegação era feita através de FragmentManager ou Intent, NavController funciona exclusivamente no contexto Compose. O back stack é armazenado como um grafo NavDestination em vez de uma pilha de Fragment. Isso elimina a sobrecarga de criar e destruir Fragments e simplifica os testes — NavController pode ser simulado através de TestNavHostController.
NavController está intimamente ligado ao NavHost — um contêiner que renderiza a tela atual do grafo. Sem NavHost, NavController não pode exibir funções composable, mas mantém a capacidade de gerenciar a pilha. Em uma arquitetura típica, NavController é criado no nível da Activity ou composable principal e passado para baixo na árvore de composição através de parâmetros.
De acordo com o Google, NavController passou por vários grandes lançamentos. A versão 2.8.0 adicionou Type-Safe Navigation, a versão 2.9.0 adicionou suporte para predictive back gesture (Android 14+). O controlador é compatível com Material3 Scaffold e BottomNavigation. Para projetos multimódulo, NavController é passado através de DI (Hilt/Koin) ou parâmetros de construtor.
NavController é criado através da função composable rememberNavController(). A função retorna uma instância de NavHostController (subclasse de NavController) vinculada ao ciclo de vida do composable atual. Ao sair da composição, o controlador é limpo. Para preservar o controlador durante a reconfiguração, use rememberSaveable ou ViewModel.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
A configuração do NavController inclui: NavHostController (principal), TestNavHostController (testes) e ScopedNavController (filho para grafos aninhados). Para BottomNavigation, NavController deve ser único para todo o aplicativo — criar um novo controlador em cada guia resultará em perda da pilha. Para passar o controlador para telas aninhadas, use um parâmetro de função em vez de CompositionLocalProvider para manter a legibilidade.
Para testar a navegação, use TestNavHostController com compose-test-rule. O controlador permite definir a rota inicial e verificar se navigate() acionou a transição esperada. Testar NavController não requer emulador — funciona com os matchers Semantics do Compose Test.
O método navigate(route: String) é o mecanismo principal de navegação no NavController. Aceita uma string de rota, NavOptions opcionais e Navigator.Extras. NavOptions controlam o comportamento da transição: launchSingleTop (não duplicar a rota na pilha), popUpTo (limpar a pilha até uma rota), restoreState (restaurar estado anterior).
NavOptions são definidos através da sintaxe builder: NavOptionsBuilder. Parâmetros principais: popUpTo (rota + inclusive/saveState), launchSingleTop (Boolean, true — não criar duplicatas), restoreState (restaurar estado ao retornar). Sem popUpTo, cada navigate() adiciona uma entrada à pilha, levando ao acúmulo de back stack e comportamento incorreto do botão Back.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras permite passar dados adicionais que não fazem parte da rota: elementos compartilhados para animação, flags Intent, bundle Pac-Man. Extras são raramente usados — principalmente para integração com Accompanist Animation ou Navigators personalizados. Para a maioria dos cenários, uma string de rota e NavOptions são suficientes.
popBackStack() é o método para retornar à tela anterior. Sem argumentos, remove a entrada do topo da pilha e retorna true se a remoção foi bem-sucedida. Se a pilha estiver vazia, o método retorna false e a Activity fecha (similar a super.onBackPressed()).
A versão sobrecarregada popBackStack(route: String, inclusive: Boolean) remove todas as entradas até a rota especificada. Se inclusive = true, a própria rota especificada também é removida. O método retorna Boolean — true se as entradas foram encontradas e removidas. A versão com inclusive é útil para cenários de “saída para a tela raiz” após autorização ou finalização de pedido.
| Método | Descrição | Exemplo |
|---|---|---|
| popBackStack() | Retornar uma tela atrás | navController.popBackStack() |
| popBackStack(route, false) | Limpar até route (route permanece) | popBackStack(“home”, false) |
| popBackStack(route, true) | Limpar até e incluindo route | popBackStack(“home”, true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | Navegar com limpeza completa | navigate(“login”) { popUpTo(0) { inclusive = true } } |
Para lidar com o botão Back do sistema (hardware back button), use BackHandler do Compose. BackHandler aceita enabled e onBack — um callback invocado ao pressionar. Para Android 14+, é usado PredictiveBackGesture, integrado via NavController desde a versão 2.9.0. Predictive back adiciona uma animação de pré-visualização do retorno.
SavedStateHandle é um mecanismo para preservar o estado do ViewModel durante navegação e reconfiguração. NavController fornece automaticamente SavedStateHandle para cada NavBackStackEntry. Através do SavedStateHandle, ViewModel armazena o estado da tela e o restaura ao retornar (restoreState = true).
No Navigation Compose, SavedStateHandle é usado junto com ViewModel: ViewModel é inicializado via SavedStateHandle, que é passado de backStackEntry. Ao navegar para outra tela e retornar (com restoreState), ViewModel recebe o estado salvo em vez de ser criado novamente. Isso é crítico para telas com entrada de dados, filtros ou rolagem.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle suporta tipos primitivos, String, Bundle e Parcelable. Para objetos complexos, salve apenas IDs e carregue os dados completos do repositório. O limite do SavedStateHandle é de aproximadamente 1 MB, excedê-lo causa TransactionTooLargeException. Para grandes volumes, use Room ou DataStore em vez de salvar no handle.
Importante: SavedStateHandle preserva o estado apenas quando restoreState = true é usado em NavOptions. Se restoreState não for especificado, ViewModel é criado novamente com valores padrão ao retornar. Para alternância de BottomNavigation com restoreState, NavController preserva o estado de cada guia e o restaura ao ser reselecionada.
currentBackStackEntryAsState() é uma função que retorna State<NavBackStackEntry?>, que atualiza a cada mudança da rota atual. Este é o mecanismo principal para sincronização da UI com a navegação: BottomNavigation destaca o item ativo, Toolbar atualiza o título, Drawer fecha ao navegar.
A função funciona através de snapshotFlow e collectAsState: quando o back stack muda, Compose recompoe os elementos inscritos. Importante: currentBackStackEntryAsState() atualiza apenas após a conclusão da animação de transição. Para atualizações imediatas, use currentDestination, que muda sincronicamente com navigate() mas não suporta estado.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
Para acessar argumentos da rota atual, use navBackStackEntry?.arguments. Isso é conveniente no BottomNavigation: selectedItem é calculado com base em currentRoute. Para depuração de navegação, use NavController.addOnDestinationChangedListener() que registra cada transição. Em produção, evite inscrições dentro de um grande número de composables — crie uma única fonte no ViewModel e passe State para a UI.
Perguntas Frequentes
Tecnicamente sim, mas não é recomendado. Um único NavController garante um back stack consistente e simplifica a depuração. Múltiplos controladores são justificados apenas para grafos aninhados com navegação separada (por exemplo, modal bottom sheet com sua própria pilha).
Passe NavController para ViewModel via construtor ou DI. No entanto, é melhor passar apenas funções callback (onNavigate, onBack) em vez do NavController em si — isso simplifica os testes. Para eventos, use Channel<NavEvent> no ViewModel e colete na UI.
O problema é o ciclo de vida: se NavController ainda não foi inicializado (NavHost não construído), navigate() é ignorado. Use LaunchedEffect para chamar a navegação após carregar os dados, não dentro de uma corrotina com ciclo de vida arbitrário.
Chame navController.navigate(“target”) { popUpTo(0) { inclusive = true } }. O parâmetro popUpTo(0) limpa a pilha completamente, inclusive = true também remove a entrada inicial. A flag launchSingleTop = true evita rotas duplicadas.
NavHostController é uma subclasse de NavController com métodos adicionais para NavHost (por exemplo, setOnBackStackChangedListener). NavController é a classe base que pode ser usada fora do NavHost para gerenciamento programático da pilha. Na maioria dos casos, NavHostController é usado.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também