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

Jetpack Compose: Navigation

Jetpack Compose: Navigation

Navigation is an essential part of every Android application. We, as Android developers, have many available approaches for navigating between screens in apps.

This talk will cover the following topics:
- Android Navigation History
- How different navigation approaches can be used together with Jetpack Compose
- How to build complex navigation in an Android app that uses Jetpack Compose

2b0404a5db1a74f01bf3bf94d142e28c?s=128

Alex Zhukovich

July 09, 2021
Tweet

Transcript

  1. @alex_zhukovich https://alexzh.com/ Jetpack Compose Navigation

  2. ANDROID NAVIGATION HISTORY 
 INTENT FRAGMENT MANAGER JETPACK NAVIGATION

  3. Activity based navigation API 1 
 2008 API 11 


  4. API 1 
 Fragment based navigation API 11 2011 


  5. API 1 
 Navigation Graph 
 2019 INTENT FRAGMENT MANAGER

    JETPACK NAVIGATION
  6. NAVIGATION USE CASES

  7. SCREEN ➔ SCREEN UPDATE PART OF THE SCREEN

  8. SCREEN ➔ SCREEN UPDATE PART OF THE SCREEN ➔

  9. ACTIVITY

  10. ACTIVITY FRAGMENT

  11. ACTIVITY FRAGMENT COMPOSABLE FUNCTION

  12. JETPACK COMPOSE 
 NAVIGATION 
 ANDROID APPS ROUTING NAVIGATION GRAPHS

  13. 42 - + @Composabl e fun Demo() { Box (

    contentAlignment = Alignment.Center , modifier = Modifier.fillMaxSize( ) ) { val count = remember { mutableStateOf(42) } Row { Text ( text = "-" , modifier = Modifier.clickable { count.value -= 1 } ) Text(text = "${count.value}" ) Text ( text = "+" , modifier = Modifier.clickable { count.value += 1 } ) } } }
  14. 42 - + @Composabl e fun Demo() { Box (

    contentAlignment = Alignment.Center , modifier = Modifier.fillMaxSize( ) ) { val count = remember { mutableStateOf(42) } Row { Text ( text = "-" , modifier = Modifier.clickable { count.value -= 1 } ) Text(text = "${count.value}" ) Text ( text = "+" , modifier = Modifier.clickable { count.value += 1 } ) } } }
  15. @Composabl e fun BasketSuccessScreen ( state: BasketState , externalRouter: Router

    , addCoffeeDrink: (Long) -> Unit , removeCoffeeDrink: (Long) -> Uni t ) { Column ( modifier = Modifier.fillMaxSize( ) ) { TopAppBar { Text ( text = "Basket" , modifier = Modifier.padding(horizontal = 12.dp) , fontSize = 18.s p ) } PaymentInfo ( deliveryCosts = BigDecimal(5) , total = state.totalPrice , currency = '€' , isPayButtonEnabled = state.products.isNotEmpty() , onPayed = { externalRouter.navigateTo("Success" ) } ) Spacer(modifier = Modifier.height(8.dp) ) ProductList ( basketProducts = state.products , onProductIncreased = removeCoffeeDrink , onProductDecreased = addCoffeeDrin k ) } }
  16. ANDROID APPS

  17. EXISTING APPS JETPACK NAVIGATION COMPOSE VIEW FRAGMENTS

  18. NEW APPS JETPACK COMPOSE NAVIGATION @COMPOSABLE FULL COMPOSABLE

  19. NAVIGATION GRAPH

  20. NavHost Destination Destination Destination NavHost( 
 navController, startDestination = Screen.Destination1.route

    
 ) { 
 composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } }
  21. @Composabl e public fun NavHost ( navController: NavHostController , startDestination:

    String , modifier: Modifier = Modifier , route: String? = null , builder: NavGraphBuilder.() -> Uni t ) { NavHost ( navController , remember(route, startDestination, builder) { navController.createGraph(
 startDestination, route, builde r ) } , modifie r ) } composable Add a NavDestination to the destination list NavGraphBuilder
  22. @Composabl e public fun NavHost ( navController: NavHostController , startDestination:

    String , modifier: Modifier = Modifier , route: String? = null , builder: NavGraphBuilder.() -> Uni t ) { NavHost ( navController , remember(route, startDestination, builder) { navController.createGraph(
 startDestination, route, builde r ) } , modifie r ) } composable Add a NavDestination to the destination list NavGraphBuilder
  23. NavHost NavGraph NavGraph Destination Destination Destination Destination

  24. NavHost( navController = navController, startDestination = "CoffeeDrinks" ) { composable("CoffeeDrinks")

    { CoffeeDrinksScreen( navigateToDetails = { navController.navigate("CoffeeDrinkDetails") }, navigateToBasket = { navController.navigate(“Basket") } ) } composable("CoffeeDrinkDetails") { CoffeeDrinkDetails() } composable("Basket") { Basket() } }
  25. NavHost( navController = navController, startDestination = "CoffeeDrinks" ) { composable("CoffeeDrinks")

    { CoffeeDrinksScreen( navigateToDetails = { navController.navigate("CoffeeDrinkDetails") }, navigateToBasket = { navController.navigate(“Basket") } ) } composable("CoffeeDrinkDetails") { CoffeeDrinkDetails() } composable("Basket") { Basket() } }
  26. NavHost( navController = navController, startDestination = "CoffeeDrinks" ) { composable("CoffeeDrinks")

    { CoffeeDrinksScreen( navigateToDetails = { navController.navigate("CoffeeDrinkDetails") }, navigateToBasket = { navController.navigate(“Basket") } ) } composable("CoffeeDrinkDetails") { CoffeeDrinkDetails() } composable("Basket") { Basket() } }
  27. public fun navigate ( route: String, builder: NavOptionsBuilder.() -> Uni

    t ) { navigate(route, navOptions(builder) ) } navigate Try to f
  28. navigate Try to f ind a destination in the graph

    NavController Back stack modi f ication based on NavOptions Add a new/existing NavBackStackEntry to the back stack Update Back stack lifecycle public fun navigate ( route: String, builder: NavOptionsBuilder.() -> Uni t ) { navigate(route, navOptions(builder) ) }
  29. ROUTING

  30. DEMO TIME

  31. ROUTING NavHost(
 navController = tabsNavController, startDestination = NavigationItem.CoffeeDrinks.rout e )

    { composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } } navController.navigate(“CoffeeDrinks”)
  32. ROUTING WITH PARAMS navController.navigate(“CoffeeDrinkDetails/$coffeeDrinkId”) NavHost(
 navController = navController, startDestination =

    "coffeeDrinks" ) { composable( route = "CoffeeDrinkDetails/{coffeeDrinkId}" ) { CoffeeDrinkDetailsScreen ( navController = navController , coffeeDrinkId = it.arguments?.getLong(“coffeeDrinkId") ?: -1 L ) } }
  33. ROUTING WITH PARAMS const val COFFEE_DRINKS_KEY = "CoffeeDrinks" const val

    COFFEE_DRINK_DETAILS_KEY = 
 "CoffeeDrinkDetails" const val FULL_COFFEE_DRINK_DETAILS_KEY = 
 “CoffeeDrinkDetails/{coffeeDrinkId}" sealed class Screen(val route: String) { object CoffeeDrinks : 
 Screen(COFFEE_DRINKS_KEY) 
 object CoffeeDrinkDetails: 
 Screen(FULL_COFFEE_DRINK_DETAILS_KEY) { fun createRoute(coffeeDrinkId: Long) = 
 "$COFFEE_DRINK_DETAILS_KEY/$coffeeDrinkId" } } navController.navigate( 
 Screen.CoffeeDrinkDetails.createRoute(42L) ) NavHost(
 navController = navController, startDestination = "coffeeDrinks" ) { composable( route = Screen.CoffeeDrinkDetails.route ) { CoffeeDrinkDetailsScreen ( navController = navController , coffeeDrinkId = it.argument s ?.getLong(“coffeeDrinkId") ?: -1 L ) } }
  34. ROUTING WITH TYPED PARAMS Integer Float Long Boolean String Resource

    reference Parcelable Serializable Enum NavHost(
 navController = navController, startDestination = "coffeeDrinks" ) { composable( route = "CoffeeDrinkDetails/{coffeeDrinkId}" , arguments = listOf ( navArgument("coffeeDrinkId") { 
 type = NavType.LongType } ) ) { CoffeeDrinkDetailsScreen ( navController = navController , coffeeDrinkId = it.argument s ?.getLong(“coffeeDrinkId") ?: -1 L ) } }
  35. DEEP LINKING <application
 ... > 
 <activit y android:name=".MainActivity "

    ... > <intent-filter > ... <action android:name="android.intent.action.VIEW" / > <category android:name="android.intent.category.DEFAULT" / > <category android:name="android.intent.category.BROWSABLE" / > <dat a android:host="example.com " android:scheme="https" / > </intent-filter > </activity > </application> AndroidManifest.xml
  36. DEEP LINKING NavHost(
 navController = tabsNavController, startDestination = Screen.CoffeeDrinks.rout e

    ) { composable( route = NavigationItem.CoffeeDrinkDetails.route , arguments = listOf ( navArgument("coffeeDrinkId") { type = NavType.LongType } ) , deepLinks = listOf ( navDeepLink { uriPattern = "$uri/CoffeeDrinkDetails/coffeeDrinkId={coffeeDrinkId}" } ) ) { CoffeeDrinkDetailsScreen ( navController = tabsNavController , coffeeDrinkId = it.arguments?.getLong("coffeeDrinkId") ?: -1 L ) } } Compose
  37. ROUTING TO EXTERNAL APP @Composabl e fun DemoScreen() { val

    context = LocalContext.curren t Button ( onClick = { val intent = Intent ( Intent.ACTION_VIEW, Uri.parse("geo:52.3676, 4.9041” ) ).apply { setPackage("com.google.android.apps.maps" ) } context.startActivity(intent ) } ) { Text ( text = "Open map" ) } }
  38. NAVIGATION OPTIONS Navigate A A B C

  39. NAVIGATION OPTIONS Navigate A B A B C navigate

  40. NAVIGATION OPTIONS Navigate A B C A B C navigate

  41. NAVIGATION OPTIONS Navigate A B C A A B C

    navigate
  42. NAVIGATION OPTIONS PopUpTo A A B C

  43. NAVIGATION OPTIONS PopUpTo A B A B C navigate

  44. NAVIGATION OPTIONS PopUpTo A B C A B C navigate

  45. NAVIGATION OPTIONS PopUpTo A B C A B C navigate(“A”)

    { popUpTo(“A”) }
  46. NAVIGATION OPTIONS PopUpTo A A A B C

  47. NAVIGATION OPTIONS PopUpTo & Inclusive A A B C

  48. NAVIGATION OPTIONS PopUpTo & Inclusive A B A B C

    navigate
  49. NAVIGATION OPTIONS PopUpTo & Inclusive A B C A B

    C navigate
  50. NAVIGATION OPTIONS PopUpTo & Inclusive A B C A B

    C navigate(“A”) { popUpTo(“A”) { inclusive = true } }
  51. NAVIGATION OPTIONS PopUpTo & Inclusive A A B C navigate(“A”)

    { popUpTo(“A”) { inclusive = true } }
  52. APPLICATION

  53. SCREEN BOTTOM NAVIGATION

  54. DIFFERENT GRAPHS interface Router { fun navigateTo(route: String ) }

    fun createRouter(
 block: (String) -> Uni t ): Router = object : Router { override fun navigateTo(route: String) { block.invoke(route ) } } class MainActivity : ComponentActivity() }
  55. DIFFERENT GRAPHS class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) setContent { OrderCoffeeTheme { val navController = rememberNavController() NavHost ( navController = navController, startDestination = Screen.Home.rout e ) { composable(Screen.Home.route) { HomeScreen ( createRouter { route - > navController.navigate(route ) } ) } ... } } } } }
  56. class MainActivity : ComponentActivity() e fun CoffeeDrinksScreen ( navigateToCoffeeDrinkDetails: (Long)

    -> Unit , viewModel: CoffeeDrinksViewModel = CoffeeDrinksViewModel( ) ) { viewModel.loadCoffeeDrinks( ) viewModel.uiState.observeAsState( initial = UiState.Loadin g ).value.let { uiState - > when (uiState) { is UiState.Loading -> { .. . } is UiState.Success -> { ...
 CoffeeDrinkList ( items = uiState.data , onCoffeeDrink = navigateToCoffeeDrinkDetails , onCoffeeDrinkCountIncreased = { viewModel.addCoffeeDrink(it ) } , onCoffeeDrinkCountDecreased = { viewModel.removeCoffeeDrink(it ) } ) } is UiState.Error -> { .. . } } } }
  57. class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContent { OrderCoffeeTheme { val navController = rememberNavController( ) NavHost(
 navController = navController, startDestination = Screen.Home.rout e ) { composable(Screen.Home.route) { CoffeeDrinksScreen ( navigateToCoffeeDrinkDetails = { navController.navigate(
 CoffeeDrinkDetail s .createRoute(it ) ) } ) }
 
 .. . } } } } } @Composabl e fun CoffeeDrinksScreen ( navigateToCoffeeDrinkDetails: (Long) -> Unit , viewModel: CoffeeDrinksViewModel = CoffeeDrinksViewModel( ) ) { viewModel.loadCoffeeDrinks( ) viewModel.uiState.observeAsState( initial = UiState.Loadin g ).value.let { uiState - > when (uiState) { is UiState.Loading -> { .. . } is UiState.Success -> { ...
 CoffeeDrinkList ( items = uiState.data , onCoffeeDrink = navigateToCoffeeDrinkDetails , onCoffeeDrinkCountIncreased = { viewModel.addCoffeeDrink(it ) } , onCoffeeDrinkCountDecreased = { viewModel.removeCoffeeDrink(it ) } ) } is UiState.Error -> { .. . } } } }
  58. THANK YOU FOR LISTENING! @alex_zhukovich https://alexzh.com/ alex-zhukovich