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

Alex Zhukovich

July 09, 2021
Tweet

More Decks by Alex Zhukovich

Other Decks in Technology

Transcript

  1. 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 } ) } } }
  2. 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 } ) } } }
  3. @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 ) } }
  4. NavHost Destination Destination Destination NavHost( 
 navController, startDestination = Screen.Destination1.route

    
 ) { 
 composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } }
  5. @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
  6. @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
  7. NavHost( navController = navController, startDestination = "CoffeeDrinks" ) { composable("CoffeeDrinks")

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

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

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

    t ) { navigate(route, navOptions(builder) ) } navigate Try to f
  11. 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) ) }
  12. ROUTING NavHost(
 navController = tabsNavController, startDestination = NavigationItem.CoffeeDrinks.rout e )

    { composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } } navController.navigate(“CoffeeDrinks”)
  13. 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 ) } }
  14. 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 ) } }
  15. 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 ) } }
  16. 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
  17. 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
  18. 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" ) } }
  19. NAVIGATION OPTIONS PopUpTo & Inclusive A B C A B

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

    { popUpTo(“A”) { inclusive = true } }
  21. 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() }
  22. 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 ) } ) } ... } } } } }
  23. 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 -> { .. . } } } }
  24. 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 -> { .. . } } } }