Navegación en Jetpack Compose con Navigation Compose
Antonio Leiva

Jetpack Compose es un cambio de paradigma enorme en muchos aspectos. Cambia la forma de pensar en casi todos los puntos involucrados en el desarrollo de una App Android.

Y la navegación no iba a ser menos.

¿Cómo se navega en Jetpack Compose? ¿Qué opciones tenemos? ¿Cómo es de difícil?

Tras organizar nuestro código para ser más escalable, en este artículo vamos a ver una introducción a la navegación en Jetpack Compose, y cómo aplicarla a tus proyectos.

¡Atención! Si quieres acceder más rápido a todo el contenido en vídeo, organizado, con contenido extra, soporte y muchas más sorpresas, puedes apuntarte gratis a mi formación Compose Expert y ver gratis el primer módulo de más de 3 horas con todo lo necesario para empezar.

Navegación en Jetpack Compose: ¿Cambia todo?

Aquí, como en muchas otras cosas, vas a tener que tomar decisiones importantes.

Si lo piensas, la realidad es que Jetpack Compose no te impone reinventar la rueda en temas de navegación.

Cuando creas una pantalla con Compose, realmente utilizas el setContent { } de la Activity (hay algunas variantes para hacerlo también en Fragments), y por tanto, podríás navegar exactamente igual que lo hacías con el sistema clásico.

Cuando el usuario haga click sobre algún componente, puedes hacer un startActivity()y navegar a una Activity nueva.

Esto te lo cuento también por si ya tienes una App escrita con el sistema antiguo, que sepas que puedes integrar Compose en cualquier pantalla de esta forma.

Pero, por supuesto, existe otra opción

Navigation Compose: Navegación entre Composables

Si recuerdas, desde hace unos años, en la forma tradicional tenemos otro sistema de navegación: Jetpack Navigation.

Mediante un grafo de navegación, podemos navegar entre Fragments o Activities de forma más estructurada. Incluso, nos permite tener un App con una única Activity, y que toda la navegación se haga mediante fragments.

Existe una variante para Jetpack Compose (aún en el alfa en el momento de escribir este artículo) que se llama Compose Navigation.

Con ella, podemos conseguir el equivalente al Single Activity en el que navegamos de un fragment a otro, pero en este caso navegamos entre Composables.

Y esto es lo que te quiero comentar hoy

Dependencia de Navigation Compose

Para poder usarlo, necesitamos incluir la siguiente dependencia:

implementation "androidx.navigation:navigation-compose:2.4.0-alpha09"

Puedes ver la última versión aquí.

Configuración de Navigation Compose

Para poder utilizarlo, necesitamos configurar algunos elementos importantes que nos ayuden a trabajar con Navigation.

La primera parte es el NavHostController. Para que que no se nos duplique, tenemos una función remember (este concepto de remember ya lo vimos en el artículo del estado en JetpackCompose) que nos va a devolver siempre el mismo navController.

Como la navegación va a ser de nivel superior (es la navegación de toda la App), podemos inicializarla directamente en el setContent { } de la MainActivity. Luego mejoraremos esto un poco:

val navController = rememberNavController()

La segunda parte es el NavHost. El NavHost es el componente de Navigation Compose que va a definir el grafo de navegación. Aquí le diremos qué navController va a usar, la pantalla de origen, y las pantallas a las que se puede navegar.

Para ello:

NavHost(
    navController = navController,
    startDestination = "main"
) {
    
}

Dentro del bloque de código, definimos con composable cada uno de los Composables a los que podemos navegar, y cuál es su ruta:

NavHost(
    navController = navController,
    startDestination = currentScreen
) {
    composable("main") {
        MainScreen()
    }
    composable("detail"){
        DetailScreen()
    }
}

La DetailScreen aún no la teníamos creada. De momento, vamos a crear algo sencillo:

@Composable
fun DetailScreen() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Detail Screen",
            style = MaterialTheme.typography.h4
        )
    }
}

¿Qué nos falta ahora? Saber cómo navegar entre los distintos elemento de navegación

Cómo navegar entre pantallas con Compose Navigation

Para navegar, necesitamos tener acceso al navController, así que vamos a pasárselo a la MainScreen:

fun MainScreen(navController: NavHostController) {
    MyMoviesApp {
        Scaffold(
            topBar = { MainAppBar() }
        ) { padding ->
            MediaList(navController, Modifier.padding(padding))
        }

    }
}

Verás que vas a tener que pasar el navController por toda la jerarquía de Composables, lo cual no es muy interesante ni a nivel de reusabilidad ni de testeo.

Pero por simplificar, de momento lo vamos a dejar así. Una vez estamos en el MediaListItem, en la Column principal solo tenemos que hacer:

Column(
    modifier = modifier
        .clickable { navController.navigate("detail") }
)

Hacemos la columna clickable gracias al modifier, y en el click le decimos que navegue al detalle.

Pero aquí está fallando algo, ¿verdad? La DetailScreen debería mostrar los detalles específicos de un elemento, y por tanto hay que pasarle un identificador como argumento.

Vamos a ver cómo hacerlo:

Cómo pasar argumentos a los Composables

Para identificar los argumentos que le pasamos a un Composable, lo hacemos a través de su route o ruta de navegación. Es tan sencillo como añadir el argumento en la ruta, aunque hay que hacer algunas cosas que necesitas saber.

Lo primero es definir exactamente cómo va a ser la ruta de detalle:

composable("detail/{mediaId}") {
    DetailScreen()
}

Si el argumento es obligatorio, se indica como ves arriba, tras una barra inclinada. Si puede ser opcional, se indica como un query param:

composable("detail?mediaId={mediaId}") {
    DetailScreen()
}

En nuestro caso, como es obligatorio que el detalle reciba un id, vamos a ir por el primer camino.

Por defecto, los argumentos son de tipo String, pero podemos configurar el tipo de la siguiente forma:

composable(
    route = "detail/{mediaId}",
    arguments = listOf(navArgument("mediaId") { type = NavType.IntType })
) {
    DetailScreen()
}

El siguiente paso es recuperar el id para pasárselo al Composable de detalle. Para ello usamos el backStackEntry que recibe la lambda de composable y accedemos a sus argumentos.

Como los argumentos son nullables, es una buena práctica requerir que nos devuelva un valor, o si no lanzar una excepción:

composable(
    route = "detail/{mediaId}",
    arguments = listOf(navArgument("mediaId") { type = NavType.IntType })
) { backStackEntry ->
    val id = backStackEntry.arguments?.getInt("mediaId")
    requireNotNull(id)
    DetailScreen(id)
}

Luego, en el click de los elementos de la lista, recuperamos el id y lo enviamos:

Column(
    modifier = modifier
        .clickable { navController.navigate("detail/${mediaItem.id}") }
) {
    ...
}

Pruébalo, verás que ya puedes navegar a la pantalla de detalle.

Creando la pantalla de detalle

De momento hemos hecho algo muy básico, pero te dejo aquí como ejercicio pintar algo un poco mejor, donde se muestre una barra superior con el nombre del item, y la foto debajo, con el icono de vídeo si procede.

Te dejo aquí el código. El composable Thumb ya lo teníamos extraído en la pantalla principal.

@Composable
fun DetailScreen(mediaId: Int) {
    val mediaItem = remember { getMedia().first { it.id == mediaId } }

    Scaffold(
        topBar = { TopAppBar(title = { Text(text = mediaItem.title) }) }
    ) {
        Thumb(mediaItem = mediaItem)
    }
}

Puedes ver todo el código en el repositorio. Echa un vistazo al commit de este artículo.

Mejorando el código

Ya tenemos todo funcionando, pero la realidad es que hay un cosas que son bastante mejorables.

  1. El navController no debería atravesar todo nuestra jerarquía de Composables, porque nos va a dar problemas tanto para testear como para las previews. Si has seguido el código, habrás visto que hemos tenido que desactivar una preview.
  2. Tenemos hardcodeadas todas las rutas de navegación y sus argumentos. Esto no es muy escalable, y lo ideal es modelar esa navegación en objetos de alguna forma
  3. No hemos visto cómo navegar hacia atrás en la vista de detalle

Todo esto te lo voy a explicar en un vídeo gratuito exclusivo de mi formación Compose Expert. Puedes apuntarte gratis ya mismo para ir a verlo.

Apúntate ahora a Compose Expert y accede a más de 3h de contenido gratuito, soporte, extras y mucho más totalmente gratis.

En el siguiente artículo, vas a ver cómo utilizar las Cards de Material Design en Jetpack Compose.

Quizá también te interese…

5 consejos para estructurar el código en Jetpack Compose

5 consejos para estructurar el código en Jetpack Compose

Escribir la interfaz con código es genial, pero puede ser que pronto se nos vaya de las manos. Aunque la forma de escribir el código de Jetpack Compose es muy natural y relativamente directa, sí que es verdad que en pro de la flexibilidad, también hay algunos...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de Antonio Leiva Gordillo, con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *