Slide 1

Slide 1 text

Navegación avanzada en Jetpack Compose Antonio Leiva Formador - GDE Kotlin / Android - Jetbrains Training Partner https://devexperto.com | @devexperto1

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Qué vamos a ver

Slide 4

Slide 4 text

Qué vamos a ver 1. Introducción

Slide 5

Slide 5 text

Qué vamos a ver 1. Introducción 2. Navegación con Compose Navigation

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

1. Introducción No os voy a engañar... La navegación en Compose es:

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

1. Introducción Con limitaciones:

Slide 16

Slide 16 text

1. Introducción Con limitaciones: — No podemos pasar Parcelables

Slide 17

Slide 17 text

1. Introducción Con limitaciones: — No podemos pasar Parcelables — No tenemos diseñador

Slide 18

Slide 18 text

1. Introducción Con limitaciones: — No podemos pasar Parcelables — No tenemos diseñador — No hay safe args

Slide 19

Slide 19 text

1. Introducción Por eso hoy quiero daros algunas claves para llevarla lo mejor posible

Slide 20

Slide 20 text

2. Navegación con Compose Navigation

Slide 21

Slide 21 text

2. Navegación con Compose Navigation Configuración en build.gradle: dependencies { implementation 'androidx.navigation:navigation-compose:2.5.1' }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

2. Navegación con Compose Navigation Navegar hacia atrás

Slide 29

Slide 29 text

2. Navegación con Compose Navigation Navegar hacia atrás composable("detail") { Detail(onUpclick = { navController.popBackStack() }) }

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

3. Navegación con argumentos 3. Al navegar, pasar el argumento composable("home") { Home(onNavigate = { navController.navigate("detail/${System.currentTimeMillis()}") }) }

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

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}" ) { /* */ } }

Slide 35

Slide 35 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 36

Slide 36 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 37

Slide 37 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 38

Slide 38 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 39

Slide 39 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 40

Slide 40 text

4. Navegación anidada navController.navigate(navItem.title) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true }

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

5. Cómo organizar el código de navegación NavCommand sealed class NavCommand( val feature: Feature, val subRoute: String, val navArgs: List = 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 } } }

Slide 47

Slide 47 text

5. Cómo organizar el código de navegación NavCommand sealed class NavCommand( val feature: Feature, val subRoute: String, val navArgs: List = 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 } } }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

6. Compose Destinations Librería de Rafael Costa que soluciona muchos de los problemas:

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

6. Compose Destinations Definir un destino de navegación @Destination @Composable fun ProfileScreen() { /*...*/ }

Slide 62

Slide 62 text

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 )

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

https://compose.expert

Slide 66

Slide 66 text

Enlaces

Slide 67

Slide 67 text

Enlaces — Proyecto de ejemplo

Slide 68

Slide 68 text

Enlaces — Proyecto de ejemplo — Compose Expert

Slide 69

Slide 69 text

Enlaces — Proyecto de ejemplo — Compose Expert — Repositorio Compose Destinations

Slide 70

Slide 70 text

¿Preguntas? ✋ https://devexperto.com | @devexperto1

Slide 71

Slide 71 text

¡Gracias! ! https://devexperto.com | @devexperto1