O componente Navigation é compatível com aplicativos Jetpack Compose. É possível navegar entre funções que podem ser compostas e, ao mesmo tempo, aproveitar a infraestrutura e os recursos do componente Navigation.
Configurar
Para oferecer compatibilidade com o Compose, use a seguinte dependência no arquivo build.gradle
do módulo do app:
dependencies { implementation "androidx.navigation:navigation-compose:1.0.0-alpha06" }
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, 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()
:
fun Profile(navController: NavController) {
...
Button(onClick = { navController.navigate("friends") }) {
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 "friends" destination
navController.navigate(“friends”) {
popUpTo("home")
}
// Pop everything up to and including the "home" destination off
// the back stack before navigating to the "friends" destination
navController.navigate("friends") {
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://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 = AmbientContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://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 = startRoute) {
...
navigation(startDestination = nestedStartRoute, route = nested) {
composable(nestedStartRoute) { ... }
}
...
}
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 BottomNavigation
que pode ser composto, descubra a NavBackStackEntry
usando
currentBackStackEntryAsState()
. Use a entrada para recuperar a
rota dos argumentos, usando a constante KEY_ROUTE
que faz parte do
NavHostController
. Usando a rota, determine se o item selecionado é o
destino atual e responda da maneira adequada definindo o rótulo, destacando
o item e navegando caso as rotas não sejam correspondentes.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite) },
label = { Text(stringResource(screen.resourceId)) },
selected = currentRoute == screen.route,
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.startDestination
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
}
}
)
}
}
}
) {
NavHost(navController, startDestination = Screen.Profile.route) {
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.
Teste
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 Navigation.