Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Navegación avanzada en Jetpack Compose

Antonio Leiva
November 28, 2022

Navegación avanzada en Jetpack Compose

La navegación en Jetpack Compose es compleja de gestionar cuando crece.

En esta charla vemos las bases de cómo hacerlo con la librería de Navigation Compose, así como algunos temas avanzados como son la navegación anidada o la organización del código.

https://github.com/antoniolg/compose-navigation-sample

Antonio Leiva

November 28, 2022
Tweet

More Decks by Antonio Leiva

Other Decks in Technology

Transcript

  1. Navegación avanzada en Jetpack Compose Antonio Leiva Formador - GDE

    Kotlin / Android - Jetbrains Training Partner https://devexperto.com | @devexperto1
  2. Qué vamos a ver 1. Introducción 2. Navegación con Compose

    Navigation 3. Navegar con argumentos 4. Navegación anidada
  3. Qué vamos a ver 1. Introducción 2. Navegación con Compose

    Navigation 3. Navegar con argumentos 4. Navegación anidada 5. Cómo organizar el código de navegación
  4. Qué vamos a ver 1. Introducción 2. Navegación con Compose

    Navigation 3. Navegar con argumentos 4. Navegación anidada 5. Cómo organizar el código de navegación 6. Librería Compose Destinations
  5. 1. Introducción No os voy a engañar... La navegación en

    Compose es: — Tediosa — Fea — Repetitiva
  6. 1. Introducción No os voy a engañar... La navegación en

    Compose es: — Tediosa — Fea — Repetitiva — Crece mal
  7. 2. Navegación con Compose Navigation Configuración en build.gradle: dependencies {

    implementation 'androidx.navigation:navigation-compose:2.5.1' }
  8. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  9. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  10. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  11. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  12. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  13. 2. Navegación con Compose Navigation Navegar entre pantallas val navController

    = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { Home(onNavigate = { navController.navigate("detail") }) } composable("detail") { Detail() } }
  14. 2. Navegación con Compose Navigation Navegar hacia atrás composable("detail") {

    Detail(onUpclick = { navController.popBackStack() }) }
  15. 3. Navegación con argumentos 1. Definir la ruta y el

    listado de argumentos con su tipo (si es String, es opcional) composable( route = "detail/{text}", arguments = listOf(navArgument("text") { type = NavType.StringType }) ) { ... }
  16. 3. Navegación con argumentos 2. Buscar el argumento una vez

    se ha navegado composable( route = "detail/{text}", arguments = listOf(navArgument("text") { type = NavType.StringType }) ) { backStackEntry -> val text = backStackEntry.arguments?.getString("text") ?: "" Detail(text, onUpclick = { navController.popBackStack() }) }
  17. 3. Navegación con argumentos 3. Al navegar, pasar el argumento

    composable("home") { Home(onNavigate = { navController.navigate("detail/${System.currentTimeMillis()}") }) }
  18. 4. Navegación anidada Bottom Navigation -> Una pila de navegación

    por cada opción navigation(startDestination = "$navDestination/home", route = navDestination) { composable( route = "$navDestination/home" ) { /* */ } composable( route = "$navDestination/detail/{text}" ) { /* */ } }
  19. 5. Cómo organizar el código de navegación Dependiendo de las

    necesidades, puedes necesitar otras formas. Yo aquí te muestro una para inspirarte
  20. 5. Cómo organizar el código de navegación Feature enum class

    Feature(val route: String) { HOME("home"), SEARCH("search"), FAVORITE("favorite"), SETTINGS("settings") }
  21. 5. Cómo organizar el código de navegación NavCommand sealed class

    NavCommand( val feature: Feature, val subRoute: String, val navArgs: List<NavArg> = emptyList() )
  22. 5. Cómo organizar el código de navegación NavCommand sealed class

    NavCommand( val feature: Feature, val subRoute: String, val navArgs: List<NavArg> = emptyList() ) { class Home(feature: Feature) : NavCommand(feature, "home") class Detail(feature: Feature) : NavCommand(feature, "detail", listOf(NavArg.ItemId)) }
  23. 5. Cómo organizar el código de navegación NavCommand sealed class

    NavCommand( val feature: Feature, val subRoute: String, val navArgs: List<NavArg> = emptyList() ) { class Home(feature: Feature) : NavCommand(feature, "home") class Detail(feature: Feature) : NavCommand(feature, "detail", listOf(NavArg.ItemId)) // search/detail/25 val route = run { val argValues = navArgs.map { "{${it.key}}" } listOf(feature.route) .plus(subRoute) .plus(argValues) .joinToString("/") } val args = navArgs.map { navArgument(it.key) { type = it.navType } } }
  24. 5. Cómo organizar el código de navegación NavCommand sealed class

    NavCommand( val feature: Feature, val subRoute: String, val navArgs: List<NavArg> = emptyList() ) { class Home(feature: Feature) : NavCommand(feature, "home") class Detail(feature: Feature) : NavCommand(feature, "detail", listOf(NavArg.ItemId)) // search/detail/25 val route = run { val argValues = navArgs.map { "{${it.key}}" } listOf(feature.route) .plus(subRoute) .plus(argValues) .joinToString("/") } val args = navArgs.map { navArgument(it.key) { type = it.navType } } }
  25. 5. Cómo organizar el código de navegación NavArg enum class

    NavArg(val key: String, val navType: NavType<*>) { ItemId("itemId", NavType.LongType) }
  26. 5. Cómo organizar el código de navegación BottomNavItem enum class

    BottomNavItem( val icon: ImageVector, val feature: Feature, @StringRes val title: Int ) { HOME(Icons.Default.Home, Feature.HOME, R.string.home), SEARCH(Icons.Default.Search, Feature.SEARCH, R.string.search), FAVORITE(Icons.Default.Favorite, Feature.FAVORITE, R.string.favorite), SETTINGS(Icons.Default.Settings, Feature.SETTINGS, R.string.settings) }
  27. 5. Cómo organizar el código de navegación Simplificar con funciones

    de extensión fun NavGraphBuilder.composable( navCommand: NavCommand, content: @Composable (NavBackStackEntry) -> Unit ) { composable( route = navCommand.route, arguments = navCommand.args, content = content ) } /* ------ */ composable(homeNavCommand) { Content(text = stringResource(navDestination.title), onClick = { navController.navigate( "$navDestination/detail/${System.currentTimeMillis()}" ) }) }
  28. 5. Cómo organizar el código de navegación Simplificar con funciones

    de extensión fun NavGraphBuilder.composable( navCommand: NavCommand, content: @Composable (NavBackStackEntry) -> Unit ) { composable( route = navCommand.route, arguments = navCommand.args, content = content ) } /* ------ */ composable(homeNavCommand) { Content(text = stringResource(navDestination.title), onClick = { navController.navigate( "$navDestination/detail/${System.currentTimeMillis()}" ) }) }
  29. 5. Cómo organizar el código de navegación Simplificar con funciones

    de extensión fun NavGraphBuilder.composable( navCommand: NavCommand, content: @Composable (NavBackStackEntry) -> Unit ) { composable( route = navCommand.route, arguments = navCommand.args, content = content ) } /* ------ */ composable(homeNavCommand) { Content(text = stringResource(navDestination.title), onClick = { navController.navigate( "$navDestination/detail/${System.currentTimeMillis()}" ) }) }
  30. 5. Cómo organizar el código de navegación fun NavGraphBuilder.bottomNavigation( navDestination:

    BottomNavItem, navController: NavHostController ) { val homeNavCommand = NavCommand.Home(navDestination.feature) val detailNavCommand = NavCommand.Detail(navDestination.feature) navigation( startDestination = homeNavCommand.route, route = navDestination.feature.route ) { composable(homeNavCommand) { Content(text = stringResource(navDestination.title), onClick = { navController.navigate( "$navDestination/detail/${System.currentTimeMillis()}" ) }) } composable(detailNavCommand) { backStackEntry -> val text = backStackEntry.arguments?.getLong(NavArg.ItemId.key) ?: "" Content(text = "$navDestination Detail: $text") } } }
  31. 5. Cómo organizar el código de navegación fun NavGraphBuilder.bottomNavigation( navDestination:

    BottomNavItem, navController: NavHostController ) { val homeNavCommand = NavCommand.Home(navDestination.feature) val detailNavCommand = NavCommand.Detail(navDestination.feature) navigation( startDestination = homeNavCommand.route, route = navDestination.feature.route ) { composable(homeNavCommand) { Content(text = stringResource(navDestination.title), onClick = { navController.navigate( "$navDestination/detail/${System.currentTimeMillis()}" ) }) } composable(detailNavCommand) { backStackEntry -> val text = backStackEntry.arguments?.getLong(NavArg.ItemId.key) ?: "" Content(text = "$navDestination Detail: $text") } } }
  32. 5. Cómo organizar el código de navegación Simplificar con funciones

    de extensión @Composable fun Navigation(navController: NavHostController) { NavHost(navController = navController, startDestination = Feature.HOME.route) { BottomNavItem.values().forEach { bottomNavigation(it, navController) } } }
  33. 6. Compose Destinations Librería de Rafael Costa que soluciona muchos

    de los problemas: — Argumentos de navegación seguros
  34. 6. Compose Destinations Librería de Rafael Costa que soluciona muchos

    de los problemas: — Argumentos de navegación seguros — Soporta Parcelable
  35. 6. Compose Destinations Librería de Rafael Costa que soluciona muchos

    de los problemas: — Argumentos de navegación seguros — Soporta Parcelable — Navegación hacia atrás y con resultados con tipos seguros
  36. 6. Compose Destinations Librería de Rafael Costa que soluciona muchos

    de los problemas: — Argumentos de navegación seguros — Soporta Parcelable — Navegación hacia atrás y con resultados con tipos seguros — Todo lo que se puede hacer con la librería oficial, pero más sencillo
  37. 6. Compose Destinations Si quieres usar argumentos @Destination @Composable fun

    ProfileScreen( id: Int, // <-- argumento de navegación obligatorio groupName: String?, // <-- argumento de navegación opcional )
  38. 6. Compose Destinations Para definir cuál es el root de

    navegación (el que carga la primera vez) @RootNavGraph(start = true) @Destination @Composable fun HomeScreen( navigator: DestinationsNavigator ) { /*...*/ navigator.navigate(ProfileScreenDestination(id = 7, groupName = "Kotlin programmers")) }
  39. 6. Compose Destinations Para definir cuál es el root de

    navegación (el que carga la primera vez) @RootNavGraph(start = true) @Destination @Composable fun HomeScreen( navigator: DestinationsNavigator ) { /*...*/ navigator.navigate(ProfileScreenDestination(id = 7, groupName = "Kotlin programmers")) }