O componente de navegação é 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.
Para conferir a biblioteca de navegação de pré-lançamento mais recente criada especificamente para o Compose, consulte a documentação do Navigation 3.
Configuração
Para oferecer suporte ao Compose, use a dependência abaixo no arquivo build.gradle do módulo do app:
Groovy
dependencies { def nav_version = "2.9.8" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.8" implementation("androidx.navigation:navigation-compose:$nav_version") }
Primeiros passos
Ao implementar a navegação em um app, implemente um host, um gráfico e um controlador de navegação. Para mais informações, consulte a visão geral da navegação.
Criar um NavController
Para informações sobre como criar um NavController no Compose, consulte a seção do Compose
em Criar um controlador de navegação.
Criar um NavHost
Para informações sobre como criar um NavHost no Compose, consulte a seção do Compose
de Projetar o gráfico de navegação.
Navegar para um elemento combinável
Para informações sobre como navegar até um elemento combinável, consulte Navegar até um destino na documentação da arquitetura.
Navegar com argumentos
Para informações sobre como transmitir argumentos entre destinos combináveis, consulte a seção do Compose em Projetar o gráfico de navegação.
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(id = "user1234"))
Objetos complexos precisam ser armazenados na forma de dados em uma única fonte 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 recuperar os argumentos responsáveis por
acessar a camada de dados no ViewModel, use o SavedStateHandle do ViewModel:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
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.
Links diretos
O Navigation Compose é compatível com links diretos que também podem ser definidos como parte da função composable(). O parâmetro deepLinks aceita uma lista de
NavDeepLink objetos que podem ser criados usando o navDeepLink()
método:
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Esses links diretos permitem associar um URL, uma ação ou um tipo MIME específico a um elemento combinável. Por padrão, esses links diretos não são expostos a apps externos. Para
disponibilizar esses links diretos externamente, adicione os elementos adequados ao arquivo manifest.xml do app
<intent-filter>. Para ativar o link
direto no exemplo anterior, adicione o código abaixo ao
<activity> elemento 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/profile/$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.
Criar uma barra de navegação adaptável e uma coluna de navegação
O NavigationSuiteScaffold mostra a interface de navegação de nível superior adequada para seu app com base na WindowSizeClass atual. Para telas compactas, o scaffold mostra uma barra de navegação inferior; para telas médias e expandidas, uma coluna de navegação.
NavigationSuiteScaffold processa a navegação principal. No entanto, layouts adaptáveis geralmente envolvem outros elementos combináveis especializados. Para os layouts canônicos de detalhes e listas e painel de suporte, que são comuns em designs adaptáveis, use
ListDetailPaneScaffold e SupportingPaneScaffold, respectivamente.
Para mais informações, consulte Criar layouts adaptáveis.
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
NavHostno 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 contêm telas com base em visualizações, telas do Compose e telas que usam o Compose e as visualizações. Quando o conteúdo de cada fragmento está no Compose, a próxima etapa é 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 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 é muito mais reutilizável e testável.
Teste
Desassocie o código de navegação dos destinos combinável para permitir o teste de cada elemento combinável isoladamente, separado do elemento combinável NavHost.
Isso significa que não é recomendado transmitir navController diretamente para qualquer
elemento combinável. Em vez disso, transmita callbacks de navegação como parâmetros. Isso permite que todos os elementos combináveis sejam testáveis individualmente, já que não exigem uma instância de navController em 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
NavControllerem si.
Por exemplo, um ProfileScreen de composição que usa um userId como entrada e permite que os usuários naveguem até a página do perfil de um amigo pode ter a seguinte assinatura:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dessa forma, o elemento combinável ProfileScreen 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.
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = 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 combináveis e as telas individuais de composição.
Como testar o NavHost
Para começar a testar seu NavHost , adicione a seguinte dependência de testes de navegação:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Encapsule o NavHost do app em um elemento combinável que aceite um NavHostController como
parâmetro.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Agora é possível testar AppNavHost e toda a lógica de navegação definida em
NavHost transmitindo uma instância do artefato de teste de navegação
TestNavHostController. Um teste de interface que verifica o destino inicial do app e o 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 interface 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 optar por realizar cliques na interface. Para aprender a testar isso de forma isolada com funções combináveis 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 atual com a esperada, usando a currentBackStackEntry de navController:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Para mais orientações sobre os conceitos básicos de testes no Compose, consulte Testar o layout do Compose e o Como testar no Jetpack Compose 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 do Compose mais avançada em um app modularizado, incluindo conceitos como gráficos aninhados e integração da barra de navegação inferior, consulte o app Now in Android no GitHub (link em inglês).
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 a navegação do Compose
- Onde elevar o estado