Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo navegar con Compose

El componente Navigation proporciona compatibilidad con aplicaciones de Jetpack Compose. Puedes navegar entre los elementos que admiten composición y aprovechar la infraestructura y las funciones del componente Navigation.

Configuración

Para admitir Compose, usa la siguiente dependencia en el archivo build.gradle del módulo de tu app:

dependencies {
    def nav_compose_version = "1.0.0-alpha01"
    implementation "androidx.navigation:navigation-compose:$nav_compose_version"
}

Cómo comenzar

NavController es la API central del componente Navigation. Tiene estado y realiza un seguimiento de la pila de actividades de elementos que admiten composición que conforman las pantallas de tu app y el estado de cada pantalla.

Puedes crear un NavController con el método rememberNavController() en el elemento que admite composición:

val navController = rememberNavController()

Debes crear el NavController en el lugar de la jerarquía correspondiente que sea accesible para todos los elementos que admitan composición y necesiten hacer referencia a él. Eso sigue los principios de la elevación de estado y te permite usar el NavController y el estado que proporciona a través de currentBackStackEntryAsState() como fuente de confianza para actualizar elementos componibles fuera de tus pantallas. Consulta Integración con la barra de navegación inferior para ver un ejemplo de esta funcionalidad.

Cómo crear un NavHost

Cada NavController debe estar asociado con un único elemento NavHost que admita composición. El NavHost vincula el NavController con un gráfico de navegación que especifica los destinos componibles que deberías poder navegar. A medida que navegas por los elementos que admiten composición, el contenido del NavHost se reescribe automáticamente. Cada destino que admite composición en tu gráfico de navegación está asociado a una ruta.

Para crear el NavHost, se requiere el NavController que se creó antes a través de rememberNavController() y la ruta del destino inicial de tu gráfico. La creación de NavHost usa la sintaxis lambda del DSL de Kotlin de Navigation para construir el gráfico de navegación. Puedes agregar el elemento a tu estructura de navegación con el método composable(), que requiere que proporciones una ruta y el elemento que admitea composición que debe vincularse al destino:

NavHost(navController, startDestination = "profile") {
    composable("profile") { Profile(...) }
    composable("friendslist") { FriendsList(...) }
    ...
}

Para navegar a un destino que admite composición en el gráfico de navegación, debes usar el método navigate(). navigate() toma un solo parámetro String que representa la ruta del destino. Para navegar desde un elemento que admite composición dentro del gráfico de navegación, llama a navigate():

fun Profile(navController: NavController) {
    ...
    Button(onClick = { navController.navigate("friends") }) {
        Text(text = "Navigate next")
    }
    ...
}

Solo debes llamar a navigate() como parte de una devolución de llamada, y no como parte de la composición, para evitar llamar a navigate() en cada recomposición.

La composición de Navigation también es compatible con el paso de argumentos entre destinos que admiten composición. Para ello, debes agregar marcadores de posición de argumento a la ruta, de manera similar a cómo agregas argumentos a un vínculo directo cuando usas la biblioteca de navegación base:

NavHost(startDestination = "profile/{userId}") {
    ...
    composable("profile/{userId}") {...}
}

De forma predeterminada, todos los argumentos se analizan como strings. Puedes especificar otro tipo con el parámetro arguments para configurar un type:

NavHost(startDestination = "profile/{userId}") {
    ...
    composable(
        "profile/{userId}",
        arguments = listOf(navArgument("userId") { type = NavType.StringType })
    ) {...}
}

Debes extraer los NavArguments de la NavBackStackEntry que está disponible en la expresión lambda de la función composable().

composable("profile/{userId}") { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

Para pasar el argumento al destino, debes agregar el valor a la ruta en lugar del marcador de posición en la llamada a navigate:

navController.navigate("profile/user1234")

Para obtener una lista de tipos admitidos, consulta Cómo pasar datos entre destinos.

Cómo agregar argumentos opcionales

La composición de Navigation también admite argumentos de navegación opcionales. Los argumentos opcionales se diferencian de los obligatorios de dos maneras:

  • Se deben incluir mediante la sintaxis del parámetro de búsqueda ("?argName={argName}").
  • Deben tener un valor defaultValue establecido o nullability = true (que configura implícitamente el valor predeterminado en null).

Eso significa que todos los argumentos opcionales se deben agregar de forma explícita a la función composable() de una lista:

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

Ahora, incluso si no se pasa ningún argumento al destino, se usará el valor defaultValue de "me" en su lugar.

La estructura de controlar los argumentos a través de las rutas significa que los elementos que admiten composición siguen siendo completamente independientes de Navigation y son mucho más fáciles de probar.

La composición de Navigation admite vínculos directos implícitos que también se pueden definir como parte de la función composable(). Agrégalos en una lista con navDeepLink():

val uri = "https://example.com"

composable(
    "profile?id={id}",
    deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("id"))
}

Esos vínculos directos te permiten asociar una URL, acción o tipo de MIME específico con un elemento que admite composición. De forma predeterminada, esos vínculos directos no se exponen a aplicaciones externas. Para que los vínculos directos estén disponibles externamente, debes agregar los elementos <intent-filter> apropiados al archivo manifest.xml de tu app. Para habilitar el vínculo directo anterior, debes agregar lo siguiente dentro del elemento <activity> del manifiesto:

<activity …>
  <intent-filter>
    ...
    <data android:scheme="https" android:host="www.example.com" />
  </intent-filter>
</activity>

Navigation vinculará automáticamente el vínculo directo a ese elemento componible cuando otra app active el vínculo en cuestión.

Esos vínculos directos también se pueden usar para compilar un PendingIntent con el vínculo apropiado de un elemento que admite composición:

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)
}

Luego, puedes usar este deepLinkPendingIntent como cualquier otro PendingIntent para abrir tu app en el destino del vínculo directo.

Integración con la barra de navegación inferior

Si defines el NavController en un nivel superior de tu jerarquía que admite composición, puedes conectar Navigation con otros componentes, como la BottomNavBar. Eso te permite navegar seleccionando los íconos de la barra inferior.

Para vincular los elementos de una barra de navegación inferior a las rutas del gráfico de navegación, se recomienda definir una clase sellada, como Screen, que se muestra aquí, que contenga la ruta y el ID de recurso de strings de los 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)
}

Luego, coloca esos elementos en una lista que pueda usar el BottomNavigationItem:

val items = listOf(
   Screen.Profile,
   Screen.FriendsList,
)

En el elemento BottomNavigation que admite composición, obtén la NavBackStackEntry mediante la función currentBackStackEntryAsState() y, con la entrada, recupera la ruta de los argumentos con la constante KEY_ROUTE que es parte de NavHostController. Con la ruta, determina si el elemento seleccionado es el destino actual. Luego, responde adecuadamente configurando la etiqueta, resaltando el elemento y navegando si las rutas no coinciden.

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) }
    }
}

Aquí aprovechas el método NavController.currentBackStackEntryAsState() para elevar el estado navController fuera de la función NavHost y compartirlo con el componente BottomNavigation. Eso significa que la BottomNavigation tiene el estado más actualizado de forma automática.

Pruebas

Te recomendamos separar el código de Navigation de los destinos componibles para habilitar la prueba de cada elemento que admite composición de forma independiente del elemento NavHost componible.

El nivel de indirección proporcionado por la expresión lambda composable es lo que te permite separar el código de Navigation del mismo elemento que admite composición. Eso funciona de dos formas:

  • Pasa argumentos analizados al elemento que admite composición.
  • Pasa expresiones lambdas que deban activarse con el elemento que admita composición para navegar, en lugar del NavController.

Por ejemplo, un elemento Profile que admite composición que recibe un userId como entrada y permite a los usuarios navegar a la página de perfil de un amigo podría tener la firma de:


@Composable
fun Profile(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 …
}

Aquí vemos que el elemento Profile que admite composición funciona independientemente de Navigation, lo que permite probarlo de manera independiente. La expresión lambda composable encapsula la lógica mínima necesaria para cerrar la brecha entre las API de Navigation y el elemento que admite composición:

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
        navController.navigate("profile?userId=$friendUserId")
}

Más información

Para obtener más información sobre Jetpack Navigation, consulta Cómo comenzar a usar el componente Navigation.