O componente Navigation é compatível com o Jetpack Escrever aplicativos. É possível navegar entre elementos combináveis além de aproveitar a infraestrutura e o atributos de machine learning.
Configurar
Para oferecer suporte ao Compose, use a dependência abaixo no módulo do app:
Arquivo build.gradle
:
Groovy
dependencies { def nav_version = "2.7.7" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" implementation("androidx.navigation:navigation-compose:$nav_version") }
Primeiros passos
Ao implementar a navegação em um app, implemente um host de navegação, gráfico e controlador. Para mais informações, consulte a visão geral da Navegação.
Criar um NavController
Para mais informações sobre como criar um NavController
no Compose, consulte a documentação do Compose.
de Criar um controlador de navegação.
Criar um NavHost
Para mais informações sobre como criar um NavHost
no Compose, consulte a seção "Escrever".
de Projetar seu gráfico de navegação.
Navegar para um elemento combinável
Para ver informações sobre como navegar até um elemento combinável, consulte Navegar até um destino na arquitetura na documentação do Google Cloud.
Navegar com argumentos
O Navigation Compose também permite transmitir argumentos entre destinos de composição. Para fazer isso, adicione 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. O parâmetro arguments
do
composable()
aceita uma lista de objetos NamedNavArgument
. Você pode
criar rapidamente um NamedNavArgument
usando o método navArgument()
e
Depois especifique o type
exato:
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
Extraia os argumentos 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 adicioná-lo à rota
ao fazer a chamada navigate
:
navController.navigate("profile/user1234")
Para ver uma lista de tipos compatíveis, consulte Transmitir dados entre destinos.
Recuperar dados complexos durante a navegação
É altamente recomendável não transmitir objetos de dados complexos ao navegar. Em vez disso, transmita as informações mínimas necessárias, como um identificador exclusivo ou outra forma de ID, como argumentos ao executar ações de navegação:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate("profile/user1234")
Objetos complexos precisam ser armazenados como dados em uma fonte única de verdade, como
a camada de dados. Depois de acessar o destino após a navegação, é possível
carregar as informações necessárias da única fonte de verdade usando o
ID transmitido. Para extrair os argumentos no ViewModel
responsável por
acessando a camada de dados, use o SavedStateHandle
da ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val userId: String = checkNotNull(savedStateHandle["userId"])
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(userId)
// …
}
Essa abordagem ajuda a evitar a perda de dados durante mudanças de configuração e qualquer inconsistência quando o objeto em questão está sendo atualizado ou modificado.
Para uma explicação mais detalhada sobre por que evitar a transmissão de dados complexos como argumentos e uma lista de tipos de argumentos aceitos, consulte Transmitir dados entre destinos.
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 comonullable = 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 = "user1234" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Mesmo que não haja nenhum argumento transmitido para o destino, o defaultValue
,
"user1234", vai ser usado.
A estrutura do processamento dos argumentos pelas rotas permite que os elementos combináveis permaneçam completamente independentes da navegação e 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()
. O parâmetro deepLinks
aceita uma lista de
Objetos NavDeepLink
que podem ser criados rapidamente usando a
Método 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 ou um tipo MIME específico a um
combinável. Por padrão, esses links diretos não são expostos a apps externos. Para
para disponibilizar esses links diretos externamente, é preciso adicionar as
<intent-filter>
ao arquivo manifest.xml
do app. Para ativar o detalhamento
no exemplo anterior, você deve adicionar o seguinte dentro do
<activity>
do manifesto:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
A navegação automaticamente estabelece um link direto para esse elemento combinável quando o link é acionado por outro app.
Esses mesmos links diretos também podem ser usados para criar um PendingIntent
com o
link direto adequado de um elemento combinável:
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
Para informações sobre como criar gráficos de navegação aninhados, consulte Gráficos aninhados.
Integração com a barra de navegação inferior
Ao definir o NavController
em um nível superior na hierarquia de composição,
você pode conectar o Navigation a outros componentes, como o componente de navegação
inferior. Isso permite que você selecione os ícones na parte inferior
barra.
Para usar os componentes BottomNavigation
e BottomNavigationItem
,
adicione a dependência androidx.compose.material
ao seu app Android.
Groovy
dependencies { implementation "androidx.compose.material:material:1.6.8" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.6.8") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Para vincular as rotas no gráfico de navegação aos itens de uma barra de navegação inferior,
é 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
de composição, extraia a NavBackStackEntry
usando a função currentBackStackEntryAsState()
. Essa entrada fornece
acesso ao NavDestination
atual. O estado selecionado de cada
O BottomNavigationItem
pode ser determinado comparando a rota do item
com a rota do destino atual e seus destinos pai para
processar casos em que você está usando a navegação aninhada com o
NavDestination
.
A rota do item também é usada para conectar a lambda onClick
a uma chamada para
navigate
. Assim, ao tocar no item você vai 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.
Segurança de tipo na navegação do Compose
O código nesta página não tem segurança de tipo. Você pode chamar o método navigate()
com rotas inexistentes ou argumentos incorretos. No entanto, é possível
estruturar o código de navegação para ter segurança de tipos durante a execução. Dessa forma, você
evita falhas e garante que:
- Os argumentos fornecidos ao navegar para um gráfico de destino ou navegação sejam do tipo certo e que todos os argumentos necessários estejam presentes.
- Os argumentos extraídos de
SavedStateHandle
sejam do tipo correto.
Para saber mais, consulte Segurança de tipos na DSL do Kotlin e na navegação. Compose.
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 forem combináveis.
Portanto, a recomendação para apps mistos do Compose e de visualizações é usar o componente de navegação baseado em fragmentos. Os fragmentos vão conter os arquivos telas, telas do Compose e telas que usam visualizações e o Compose. Uma vez que cada O conteúdo do fragmento está no Compose. A próxima etapa é unir todas essas telas. com o 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 combinável na hierarquia:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
No fragmento, você procura a NavController
e navega até o
destino para criar uma ponte entre o Compose e o componente
de navegação baseado em fragmento:
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.
Teste
Separe o código de navegação dos destinos combináveis para permitir testes
cada elemento combinável isoladamente, separado do NavHost
.
Isso significa que você não deve transmitir o navController
diretamente para nenhum
combinável e, em vez disso, transmitir callbacks de navegação como parâmetros. Isso permite
todos os elementos combináveis sejam testáveis individualmente, já que não exigem uma
instância de navController
nos testes.
O nível de indireção fornecido pela lambda composable
é o que permite
separar o código de navegação do elemento combinável. Isso funciona em duas
direções:
- Transmitir para a função de composição apenas argumentos analisados
- Transmitir lambdas que vão ser acionadas pelo elemento combinável para navegar,
em vez da
NavController
em si.
Por exemplo, um elemento Profile
combinável que usa um userId
como entrada e permite
os usuários para navegar até a página de perfil de um amigo podem ter a assinatura de:
@Composable
fun Profile(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dessa forma, o elemento combinável Profile
funciona de maneira independente da navegação,
permitindo que ele seja testado em separado. A lambda composable
encapsula
a lógica mínima necessária para preencher a lacuna entre as APIs de navegação
e o elemento combinável.
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
navController.navigate("profile?userId=$friendUserId")
}
}
É recomendável programar testes que atendam aos requisitos de navegação do seu app
testando o NavHost
, as ações de navegação transmitidas
para os elementos e as telas individuais de composição.
Como testar o NavHost
Para começar a testar o NavHost
, adicione o seguinte teste de navegação.
dependência:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
É possível configurar o assunto do teste NavHost
e transmitir uma
instância da instância navController
para ele. Para isso, a navegação
o artefato de teste fornece um TestNavHostController
. Um teste de IU que
verifica o destino inicial do app e NavHost
teria esta aparência:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
Como testar ações de navegação
Você pode testar a implementação da navegação de várias maneiras. Para isso, clique nos elementos da IU e confira o destino mostrado ou compare a rota esperada com a rota exibida.
Como o objetivo é testar a implementação concreta do app, recomendamos clicar na IU. Para aprender a testar isso de forma isolada com funções de composição individuais, confira o codelab Como testar no Jetpack Compose.
Também é possível usar o navController
para conferir suas declarações
ao comparar a rota de string atual com a esperada, usando a
currentBackStackEntry
de navController
:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
val route = navController.currentBackStackEntry?.destination?.route
assertEquals(route, "profiles")
}
Para mais orientações sobre os conceitos básicos de testes no Compose, consulte Como testar o layout do Compose e Como testar no Jetpack Compose. o codelab. Para saber mais sobre testes avançados do código de navegação, consulte o guia Testar a navegação.
Saiba mais
Para saber mais sobre a navegação do Jetpack, consulte Como usar o componente de navegação ou faça o codelab de navegação do Jetpack Compose.
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.
Para saber mais sobre uma implementação de navegação mais avançada no Compose em um app modularizado, incluindo conceitos como gráficos aninhados e barra de navegação na parte de baixo da tela. consulte o app Now in Android (link em inglês) no GitHub.
Amostras
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Material Design 2 no Compose
- Migrar a navegação do Jetpack para o Navigation Compose
- Para onde elevar o estado