O componente de navegação é compatível com aplicativos do Jetpack Compose. É possível navegar entre funções que podem ser compostas e, ao mesmo tempo, aproveitar a infraestrutura e os recursos do componente de navegação.
Configurar
Para oferecer compatibilidade com o Compose, use a dependência a seguir no arquivo
build.gradle
do módulo do app:
Groovy
dependencies { def nav_version = "2.5.1" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { def nav_version = "2.5.1" implementation("androidx.navigation:navigation-compose:$nav_version") }
Primeiros passos
NavController
é a API central do componente Navigation. É
uma função com estado que acompanha a pilha de funções que podem ser compostas, as quais, por sua vez, criam as
telas do app e o estado de cada tela.
É possível criar um NavController
usando o método rememberNavController()
na função que pode ser composta:
val navController = rememberNavController()
Na hierarquia de funções que podem ser compostas, crie o NavController
em um local
onde todas as funções que precisem referenciá-lo tenham acesso a ele. Isso está de acordo
com os princípios da elevação de estado
e permite que você use o NavController
e o estado apresentado por ele
por currentBackStackEntryAsState()
, para que essa função seja usada como a fonte da verdade para atualizar
funções que podem ser compostas fora das telas. Consulte Integração com a barra de navegação
inferior para ver um exemplo dessa funcionalidade função
Como criar um NavHost
Cada NavController
precisa ser associado a um único NavHost
que pode ser composto. NavHost
vincula NavController
a um gráfico de navegação que especifica entre quais
destinos que podem ser compostos você pode navegar. À medida
que você navega entre as funções que podem ser compostas, o conteúdo do NavHost
é automaticamente
recomposto. Cada destino
composto no gráfico de navegação está associado a uma rota.
Para criar NavHost
é necessário informar o NavController
criado anteriormente por
rememberNavController()
e a rota do destino inicial do gráfico.
A criação de NavHost
usa a sintaxe lambda da DSL Kotlin do Navigation para construir seu
gráfico de navegação. É possível adicionar à estrutura de navegação usando o
método composable()
. Esse método exige uma rota e a
função que pode ser composta que estará vinculada ao destino:
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { Profile(/*...*/) }
composable("friendslist") { FriendsList(/*...*/) }
/*...*/
}
Como navegar para uma função que pode ser composta
No gráfico de navegação, use o
método navigate()
para navegar até um destino que pode ser composto. navigate()
usa um único parâmetro String
que
representa a rota do destino. Para navegar de um destino dentro do gráfico
de navegação, chame navigate()
:
@Composable
fun Profile(navController: NavController) {
/*...*/
Button(onClick = { navController.navigate("friendslist") }) {
Text(text = "Navigate next")
}
/*...*/
}
Chame navigate()
apenas como parte de um callback e não como parte do
elemento que pode ser composto, a fim de evitar chamar navigate()
em cada recomposição.
Por padrão, navigate()
adiciona seu novo destino à pilha de retorno. Você pode
modificar o comportamento de navigate
anexando outras opções de navegação à
nossa chamada navigate()
:
// Pop everything up to the "home" destination off the back stack before
// navigating to the "friendslist" destination
navController.navigate("friendslist") {
popUpTo("home")
}
// Pop everything up to and including the "home" destination off
// the back stack before navigating to the "friendslist" destination
navController.navigate("friendslist") {
popUpTo("home") { inclusive = true }
}
// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
launchSingleTop = true
}
Consulte o guia do popUpTo para ver mais casos de uso.
Navegar com argumentos
O Navigation Compose também permite transmitir argumentos entre destinos que podem ser compostos. Para fazer isso, é necessário adicionar marcadores de argumentos à rota, de forma semelhante a como você adiciona argumentos a um link direto ao usar a biblioteca de navegação de base:
NavHost(startDestination = "profile/{userId}") {
...
composable("profile/{userId}") {...}
}
Por padrão, todos os argumentos são analisados como strings É possível especificar outro tipo
usando o parâmetro arguments
para definir um type
:
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
Extraia NavArguments
de NavBackStackEntry
, disponível no lambda
da função composable()
.
composable("profile/{userId}") { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Para transmitir o argumento ao destino, é preciso adicionar o valor à rota
no lugar do marcador na chamada para navigate
:
navController.navigate("profile/user1234")
Para ver uma lista de tipos compatíveis, consulte Transmitir dados entre destinos.
Como adicionar argumentos opcionais
O Navigation Compose também é compatível com argumentos de navegação opcionais. Os argumentos opcionais são diferentes dos obrigatórios de duas maneiras:
- Eles precisam ser incluídos usando a sintaxe de parâmetros de consulta (
"?argName={argName}"
) - Eles precisam ter um conjunto de
defaultValue
ou ser definidos comonullability = true
(que define o valor padrão comonull
de forma implícita)
Isso significa que todos os argumentos opcionais precisam ser adicionados de forma explícita
à função composable()
como uma lista:
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Mesmo que não haja nenhum argumento transmitido para o destino, o defaultValue
de "me" será usado.
A estrutura do processamento dos argumentos pelas rotas permite que as funções que podem ser compostas permaneçam completamente independentes do Navigation e sejam muito mais testáveis.
Links diretos
O Navigation Compose é compatível com links diretos implícitos, que também podem ser definidos como
parte da função composable()
. Adicione esses links como uma lista usando navDeepLink()
:
val uri = "https://www.example.com"
composable(
"profile?id={id}",
deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("id"))
}
Esses links diretos permitem associar um URL, uma ação e/ou um tipo MIME específico a
uma função que pode ser composta. Por padrão, esses links diretos não são expostos a apps externos. Para
disponibilizar esses links diretos externamente, adicione os elementos <intent-filter>
adequados ao arquivo manifest.xml
do app. Para ativar o
link direto acima, adicione o seguinte ao elemento <activity>
do manifesto:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
O Navigation automaticamente terá um link direto para a função que pode ser composta quando o link for acionado por outro app.
Esses mesmos links diretos também podem ser usados para criar um PendingIntent
com o
link direto adequado de uma função que pode ser composta:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
deepLinkPendingIntent
pode ser usada da mesma forma que qualquer outra PendingIntent
para abrir seu app no destino do link direto.
Navegação aninhada
Os destinos podem ser agrupados em um gráfico aninhado para modularizar um fluxo específico na IU do app. Um exemplo disso é um fluxo de login independente.
O gráfico aninhado encapsula os próprios destinos. Como no gráfico raiz, um gráfico aninhado precisa ter um destino identificado como o inicial pela rota dele. Este é o destino que é acessado quando você navega até a rota associada ao gráfico aninhado.
Para adicionar um gráfico aninhado ao seu NavHost
, use a
função de extensão navigation
:
NavHost(navController, startDestination = "home") {
...
// Navigating to the graph via its route ('login') automatically
// navigates to the graph's start destination - 'username'
// therefore encapsulating the graph's internal routing logic
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
...
}
É altamente recomendável dividir seu gráfico de navegação em vários métodos à medida que o tamanho dele aumenta. Dessa forma, vários módulos também poderão contribuir com gráficos de navegação próprios.
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
}
Ao transformar o método em um método de extensão em NavGraphBuilder
, você pode o usar
com os métodos de extensão pré-criados navigation
, composable
e dialog
:
NavHost(navController, startDestination = "home") {
...
loginGraph(navController)
...
}
Integração com a barra de navegação inferior
Ao definir o NavController
em um nível superior na hierarquia de funções que podem ser compostas,
é possível conectar o Navigation a outros componentes, como BottomNavBar
.
Isso permite que você selecione os ícones na barra de navegação interior.
Para vincular os itens em uma barra de navegação inferior às rotas no gráfico de navegação,
é recomendável definir uma classe selada, como a Screen
mencionada aqui, que
contenha o ID de recurso da string e da rota para o destinos.
sealed class Screen(val route: String, @StringRes val resourceId: Int) {
object Profile : Screen("profile", R.string.profile)
object FriendsList : Screen("friendslist", R.string.friends_list)
}
Em seguida, coloque esses itens em uma lista que possa ser usada pelo BottomNavigationItem
:
val items = listOf(
Screen.Profile,
Screen.FriendsList,
)
No seu BottomNavigation
que pode ser composto, extraia a NavBackStackEntry
usando a função currentBackStackEntryAsState()
. Essa entrada fornece
acesso ao NavDestination
atual. O estado selecionado de cada
BottomNavigationItem
pode ser determinado comparando a rota do item
com a rota do destino atual e os destinos pai dele, para
processar casos em que você está usando a navegação aninhada com o
método auxiliar hierarchy
.
A rota do item também é usada para conectar a lambda onClick
a uma chamada para
navigate
. Assim, ao tocar no item você navegará até ele. Usando
as sinalizações saveState
e restoreState
, o estado e a backstack desse
item são salvos e restaurados corretamente à medida que você alterna entre os itens da navegação
inferior.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Screen.Profile.route, Modifier.padding(innerPadding)) {
composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.FriendsList.route) { FriendsList(navController) }
}
}
Aqui você aproveita o método NavController.currentBackStackEntryAsState()
para elevar o estado de navController para fora da função NavHost e compartilhá-lo
com o componente BottomNavigation
. Isso significa que BottomNavigation
automaticamente tem o estado mais atualizado.
Interoperabilidade
Se quiser usar o componente de navegação com o Compose, você tem duas opções:
- Definir um gráfico de navegação com o componente de navegação para fragmentos.
- Definir um gráfico de navegação com um
NavHost
no Compose usando destinos do Compose. Isso só é possível se todas as telas no gráfico de navegação puderem ser compostas.
Portanto, a recomendação para apps híbridos é usar o componente de navegação com base em fragmentos e usar fragmentos para armazenar telas com base em visualização, telas do Compose e telas que usam ambos os tipos de visualizações e o Compose. Quando cada fragmento de tela no app for um wrapper em torno de uma função que pode ser composta, a próxima etapa será unir todas essas telas ao Navigation Compose e remover todos os fragmentos.
Navegar do Compose usando o componente de navegação para fragmentos
Para alterar os destinos no código do Compose, você expõe eventos que podem ser transmitidos e acionados por qualquer função que pode ser composta na hierarquia:
@Composable
fun MyScreen(onNavigate: (Int) -> ()) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
No fragmento, você transforma a ponte entre o Compose e o componente
de navegação com base em fragmento encontrando o NavController
e navegando até o
destino:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Como alternativa, você pode transmitir o NavController
pela hierarquia do Compose.
No entanto, a exposição de funções simples é muito mais reutilizável e testável.
Como testar
Recomendamos que você desassocie o código do Navigation dos
destinos que podem ser compostos, para permitir o teste de cada composto isoladamente, separados
do NavHost
que pode ser composto.
O nível de indireção fornecido pelo lambda composable
é o que permite
separar o código do Navigation da função que pode ser composta. Isso funciona em duas
direções:
- Transmitir apenas argumentos analisados para a função que pode ser composta
- Passar lambdas que serão acionados pela função que pode ser composta para navegar,
em vez do próprio
NavController
.
Por exemplo, um Profile
que pode ser composto que usa um userId
como entrada e permite
que os usuários naveguem até a página de perfil de um amigo pode ter a assinatura de:
@Composable
fun Profile(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
É possível ver que o Profile
que pode ser composto funciona de forma independente do Navigation,
permitindo que ele seja testado de forma independente. O lambda composable
encapsula
a lógica mínima necessária para preencher a lacuna entre as APIs Navigation
e a função que pode ser composta:
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
navController.navigate("profile?userId=$friendUserId")
}
}
Saiba mais
Para saber mais sobre o Jetpack Navigation, consulte Primeiros passos com o componente de navegação ou faça o codelab do Jetpack Compose Navigation.
Para aprender a projetar a navegação do app para que ele se adapte a diferentes tamanhos, orientações e formatos de tela, consulte Navegação para IUs responsivas.