O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Como navegar com o Compose

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 {
    def nav_compose_version = "1.0.0-alpha01"
    implementation "androidx.navigation:navigation-compose:$nav_compose_version"
}

Primeiros passos

NavController é a API central do componente Navigation. É uma função com estado e controla a pilha de funções que podem ser compostas que 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()

Crie o NavController em um local na hierarquia de funções que podem ser compostas em que 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(...) }
    ...
}

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 a partir de um destino no 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 destino, a fim de evitar chamar navigate() em cada recomposição.

O Navigation Compose também permite passar 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 para o 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 como nullability = true (que define o valor padrão como null 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.

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 = ...
val context = ContextAmbient.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 usado da mesma forma que qualquer outro PendingIntent para abrir seu app no destino do link direto.

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 = {
                        // This is the equivalent to popUpTo the start destination
                        navController.popBackStack(navController.graph.startDestination, false)

                        // This if check gives us a "singleTop" behavior where we do not create a
                        // second instance of the composable if we are already on that destination
                        if (currentRoute != screen.route) {
                            navController.navigate(screen.route)
                        }
                    }
                )
            }
        }
    }
) {

    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.