---
title: "Navigation Compose: Guia Completo do NavHost e NavController | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/navigation-compose-navhost-navcontroller-kotlin-2026/"
markdown_url: "https://kotlin.dev.br/blog/navigation-compose-navhost-navcontroller-kotlin-2026.MD"
description: "Aprenda Navigation Compose no Android: NavHost, NavController, rotas type-safe, bottom navigation, grafos aninhados, deep links e boas práticas com exemplos em Kotlin."
date: "2026-06-26"
author: "Karina Melo"
---

# Navigation Compose: Guia Completo do NavHost e NavController | Kotlin Brasil

Aprenda Navigation Compose no Android: NavHost, NavController, rotas type-safe, bottom navigation, grafos aninhados, deep links e boas práticas com exemplos em Kotlin.


Se você já construiu uma tela com [Jetpack Compose](/guias/guia-jetpack-compose/), em algum momento precisa responder a uma pergunta inevitável: **como o usuário sai daqui e vai para lá?** A resposta canônica, estável e amplamente adotada em produção é o **Navigation Compose** — o componente do AndroidX que traz `NavHost`, `NavController` e grafos de navegação declarativos para o mundo das funções composáveis.

Neste guia completo, você vai dominar o Navigation Compose do zero ao avançado: configuração, `NavHost`, `NavController`, rotas type-safe, argumentos, bottom navigation, grafos aninhados, deep links, salvamento de estado e boas práticas de arquitetura. Se você está começando, leia antes o [tutorial de Compose para iniciantes](/tutoriais/jetpack-compose-basico/) e o conteúdo sobre [Kotlin para Android](/blog/kotlin-para-android/) para fixar a base.

## O que é o Navigation Compose?

O Navigation Compose é a integração oficial da biblioteca **AndroidX Navigation** com o Jetpack Compose. Enquanto a navegação clássica nasceu no mundo de Activities e Fragments (`NavigationUI`, `NavHostFragment`, gráficos em XML), esta versão substitui o XML por **composables** e expõe o `NavController` como algo que você segura no nível do seu app.

Os pilares são três:

1. **`NavController`** — o objeto central que conhece a back stack, o destino atual e expõe `navigate()` e `popBackStack()`.
2. **`NavHost`** — um composable que desenha o destino correspondente ao estado atual do `NavController`. É nele que você registra todas as telas.
3. **Destination / Route** — a definição de cada tela, que pode receber argumentos tipados e ser protegida por lógica.

A grande vantagem sobre soluções caseiras (listas de estado com `when`) é que o Navigation Compose resolve, de forma testada, os problemas difíceis que aparecem em todo app real: **back stack**, **state saving**, **deep links**, **transições**, **resultado entre telas** e **integração com o botão de voltar do sistema**.

## Dependências e configuração

No seu `build.gradle.kts` (módulo `:app`), adicione o artefato `navigation-compose`:

```kotlin
dependencies {
    val navVersion = "2.8.5"
    implementation("androidx.navigation:navigation-compose:$navVersion")
}
```

Se você gerencia versões em catálogo — o que recomendamos, veja o [guia de Version Catalog](/blog/gradle-version-catalog-kotlin-2026/) — basta registrar a lib no `libs.versions.toml`:

```toml
[versions]
navigationCompose = "2.8.5"

[libraries]
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
```

```kotlin
implementation(libs.androidx.navigation.compose)
```

## NavController: o coração da navegação

A regra de ouro do Navigation Compose é **uma única instância de `NavController` por app**, geralmente retida no nível mais alto da sua UI. Em Compose, você a cria com `rememberNavController()`:

```kotlin
@Composable
fun KotlinBrasilApp() {
    val navController = rememberNavController()
    KotlinBrasilTheme {
        Scaffold { innerPadding ->
            KotlinNavHost(
                navController = navController,
                modifier = Modifier.padding(innerPadding),
            )
        }
    }
}
```

Por que segurar o `NavController` lá em cima? Porque a barra inferior, a top bar e o `NavHost` precisam enxergar o mesmo controlador para reagir ao destino atual (por exemplo, esconder a bottom bar na tela de login). Em apps que usam injeção de dependência, o [guia de Hilt com Compose](/blog/hilt-android-kotlin-modulos-scopes-testes-2026/) mostra como fornecer ViewModels que conversam com a navegação sem acoplar UI a regras de negócio.

## NavHost: registrando destinos

O `NavHost` recebe o `NavController`, o destino inicial e um bloco `composable { }` para cada tela:

```kotlin
@Composable
fun KotlinNavHost(
    navController: NavController,
    modifier: Modifier = Modifier,
) {
    NavHost(
        navController = navController,
        startDestination = "home",
        modifier = modifier,
    ) {
        composable("home") {
            HomeScreen(
                onOpenArticle = { id -> navController.navigate("article/$id") },
            )
        }
        composable(
            route = "article/{articleId}",
            arguments = listOf(navArgument("articleId") { type = NavType.StringType }),
        ) { backStackEntry ->
            val articleId = backStackEntry.arguments?.getString("articleId").orEmpty()
            ArticleScreen(articleId = articleId)
        }
    }
}
```

Aqui já aparecem três conceitos chave: a rota (`"home"`, `"article/{articleId}"`), o **placeholder de argumento** (`{articleId}`) e o `navArgument` que define o tipo esperado. O `backStackEntry` entregue ao composable de destino carrega os argumentos já parseados.

## Rotas type-safe com objetos serializáveis

A partir do AndroidX Navigation **2.8.0**, você pode abandonar as strings mágicas e usar **rotas serializáveis**, que dão segurança de tipos em tempo de compilação. Defina cada destino como um `object` ou `data class` marcado com `@Serializable` (conceito explicado em detalhe no nosso [glossário de Serialization](/glossario/serialization/) e no artigo de [serialização avançada e polimorfismo](/blog/kotlin-serialization-avancada-polimorfismo-2026/)):

```kotlin
import kotlinx.serialization.Serializable

@Serializable object Home
@Serializable data class Article(val articleId: String)
@Serializable data class Search(val query: String = "")

@Composable
fun KotlinNavHost(navController: NavController) {
    NavHost(navController, startDestination = Home) {
        composable<Home> {
            HomeScreen(onOpenArticle = { id ->
                navController.navigate(Article(id))
            })
        }
        composable<Article> { backStackEntry ->
            val article: Article = backStackEntry.toRoute()
            ArticleScreen(articleId = article.articleId)
        }
        composable<Search> { backStackEntry ->
            val search: Search = backStackEntry.toRoute()
            SearchScreen(initialQuery = search.query)
        }
    }
}
```

As vantagens são diretas: o compilador reclama se você passar o tipo errado, não há concatenação de string para montar URLs e os argumentos opcionais viram parâmetros com valor padrão. Sempre que possível, prefira esta abordagem em vez de rotas como string.

## Argumentos opcionais e valores padrão

Argumentos opcionais precisam de `defaultValue` e de `nullable = true`. Com rotas type-safe, isso desaparece — basta usar `val query: String = ""`. Se você ainda está em strings, o equivalente é:

```kotlin
composable(
    route = "search?q={query}",
    arguments = listOf(navArgument("query") {
        type = NavType.StringType
        defaultValue = ""
        nullable = true
    }),
)
```

## Bottom navigation integrada

Um padrão clássico é casar `NavigationBar` (Material 3) com o `NavHost`. O truque é manter a back stack do destino raiz de cada aba para preservar estado ao trocar de tab:

```kotlin
@Composable
fun MainScreen(navController: NavController) {
    val items = listOf(Tab.Home, Tab.Search, Tab.Profile)
    Scaffold(
        bottomBar = {
            val currentBackStack by navController.currentBackStackEntryAsState()
            val currentDestination = currentBackStack?.destination
            NavigationBar {
                items.forEach { tab ->
                    NavigationBarItem(
                        selected = currentDestination?.hierarchy?.any { it.route == tab.route } == true,
                        onClick = {
                            navController.navigate(tab.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        },
                        icon = { Icon(tab.icon, contentDescription = tab.label) },
                        label = { Text(tab.label) },
                    )
                }
            }
        },
    ) { innerPadding ->
        KotlinNavHost(navController, Modifier.padding(innerPadding))
    }
}
```

Os detalhes que fazem a diferença são: `popUpTo` com `saveState = true`, `launchSingleTop = true` e `restoreState = true`. Sem eles, toda vez que o usuário volta a uma aba você recria a tela do zero e perde a posição de rolagem. O design da barra segue as recomendações de [Material 3 Expressive com Compose](/blog/material-3-expressive-compose-kotlin-2026/) e os [layouts de Compose](/tutoriais/jetpack-compose-layouts/).

## Grafos aninhados

Para fluxos fechados (checkout, onboarding, wizard), use `navigation { }` dentro do `NavHost` para criar um subgrafo com sua própria rota de entrada:

```kotlin
navigation(startDestination = Checkout.Cart, route = "checkout") {
    composable<Checkout.Cart> { CartScreen(onNext = { navController.navigate(Checkout.Address) }) }
    composable<Checkout.Address> { AddressScreen(onNext = { navController.navigate(Checkout.Payment) }) }
    composable<Checkout.Payment> { PaymentScreen(onDone = { navController.popBackStack() }) }
}
```

Assim você navega para `"checkout"` e o usuário fica preso ao fluxo até terminar — o botão de voltar do sistema respeita a back stack interna.

## Deep links e App Links

O `NavHost` aceita `deepLinks` por destino, conectando rotas internas a URLs reais (o que casca bem com o guia de [App Links e deep links com Kotlin](/blog/app-links-deep-links-android-kotlin-2026/)):

```kotlin
composable<Article>(
    deepLinks = listOf(navDeepLink { uriPattern = "https://kotlin.dev.br/artigos/{articleId}" }),
) { entry ->
    val article: Article = entry.toRoute()
    ArticleScreen(article.articleId)
}
```

Depois registre o `intent-filter` correspondente no `AndroidManifest.xml` para que links externos (notificações, e-mails, busca) abram direto na tela certa.

## Resultado entre telas

Para devolver um valor (selecionar um item, criar algo), use `savedStateHandle`:

```kotlin
// Tela que pede um resultado
val result = navController.currentBackStackEntry
    ?.savedStateHandle
    ?.getStateFlow<String>("selectedCategory", "")
    ?.collectAsState()

navController.navigate("categoryPicker")

// Tela que devolve o resultado
navController.previousBackStackEntry?.savedStateHandle?.set("selectedCategory", "coroutines")
navController.popBackStack()
```

Como o `savedStateHandle` sobrevive a mudanças de configuração, o resultado chega de forma confiável. Para testar esses fluxos reativos, o [guia de testes com Turbine e StateFlow](/blog/testando-flow-stateflow-turbine-kotlin-2026/) é leitura obrigatória.

## Estado, back stack e boas práticas

Algumas regras que evitam bugs difíceis em produção:

- **Nunca** passe o `NavController` para dentro de ViewModels ou repositórios. A navegação é uma preocupação de UI.
- Centralize as rotas em um único lugar (objetos `@Serializable` ou constantes) para evitar strings duplicadas.
- Use `popUpTo` com cuidado: esquecer o `saveState` é a causa nº 1 de telas que "resetam" ao trocar de aba.
- Para telas que devem sobreviver a morte do processo, confie no `savedStateHandle` e persista dados críticos em [DataStore](/tutoriais/datastore-preferences-kotlin/), não em `remember`.
- Modele estados de UI com [sealed classes](/blog/sealed-classes-kotlin/) para que cada destino saiba exatamente quais eventos de navegação pode emitir.

## Quando considerar Navigation 3

O Navigation Compose descrito aqui é a solução **estável** e é o que a maioria dos apps em produção usa hoje. Existe uma versão mais nova, o **Navigation 3**, que trata a navegação como estado puro com back stack tipada e integração ainda mais idiomática com Compose. Vale acompanhar — já cobrimos o [guia do Navigation 3 com Compose](/blog/navigation-3-compose-android-2026/) — mas, para novos projetos em 2026 que precisam de estabilidade e material de referência maduro, o Navigation Compose clássico continua sendo a escolha segura.

## Conclusão

O Navigation Compose é a fundação de navegação do Android moderno. Dominar `NavController`, `NavHost`, rotas type-safe, bottom navigation, grafos aninhados, deep links e `savedStateHandle` te coloca em posição de arquitetar qualquer fluxo — do app mais simples ao e-commerce com checkout multistep. Comece com rotas serializáveis desde o primeiro dia, mantenha o `NavController` isolado na UI e deixe que padrões como `saveState`/`restoreState` façam o trabalho pesado de preservar a experiência do usuário.

Se quiser ir além, conecte navegação a boas práticas de [injeção de dependência com Hilt](/blog/hilt-android-kotlin-modulos-scopes-testes-2026/), [layouts de Compose](/tutoriais/jetpack-compose-layouts/) e [persistência com DataStore](/tutoriais/datastore-preferences-kotlin/) para construir apps Android robustos e mantíveis com Kotlin.
