Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ANDROID NAVIGATION HISTORY 
 INTENT FRAGMENT MANAGER JETPACK NAVIGATION

Slide 3

Slide 3 text

Activity based navigation API 1 
 2008 API 11 


Slide 4

Slide 4 text

API 1 
 Fragment based navigation API 11 2011 


Slide 5

Slide 5 text

API 1 
 Navigation Graph 
 2019 INTENT FRAGMENT MANAGER JETPACK NAVIGATION

Slide 6

Slide 6 text

NAVIGATION USE CASES

Slide 7

Slide 7 text

SCREEN ➔ SCREEN UPDATE PART OF THE SCREEN

Slide 8

Slide 8 text

SCREEN ➔ SCREEN UPDATE PART OF THE SCREEN ➔

Slide 9

Slide 9 text

ACTIVITY

Slide 10

Slide 10 text

ACTIVITY FRAGMENT

Slide 11

Slide 11 text

ACTIVITY FRAGMENT COMPOSABLE FUNCTION

Slide 12

Slide 12 text

JETPACK COMPOSE 
 NAVIGATION 
 ANDROID APPS ROUTING NAVIGATION GRAPHS

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

ANDROID APPS

Slide 17

Slide 17 text

EXISTING APPS JETPACK NAVIGATION COMPOSE VIEW FRAGMENTS

Slide 18

Slide 18 text

NEW APPS JETPACK COMPOSE NAVIGATION @COMPOSABLE FULL COMPOSABLE

Slide 19

Slide 19 text

NAVIGATION GRAPH

Slide 20

Slide 20 text

NavHost Destination Destination Destination NavHost( 
 navController, startDestination = Screen.Destination1.route 
 ) { 
 composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } }

Slide 21

Slide 21 text

@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

Slide 22

Slide 22 text

@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

Slide 23

Slide 23 text

NavHost NavGraph NavGraph Destination Destination Destination Destination

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

public fun navigate ( route: String, builder: NavOptionsBuilder.() -> Uni t ) { navigate(route, navOptions(builder) ) } navigate Try to f

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

ROUTING

Slide 30

Slide 30 text

DEMO TIME

Slide 31

Slide 31 text

ROUTING NavHost(
 navController = tabsNavController, startDestination = NavigationItem.CoffeeDrinks.rout e ) { composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) } composable(“Basket”) { BasketScreen(...) } composable(“Profile”) { Destination1Screen(...) } } navController.navigate(“CoffeeDrinks”)

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

DEEP LINKING 
 ... AndroidManifest.xml

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

NAVIGATION OPTIONS Navigate A A B C

Slide 39

Slide 39 text

NAVIGATION OPTIONS Navigate A B A B C navigate

Slide 40

Slide 40 text

NAVIGATION OPTIONS Navigate A B C A B C navigate

Slide 41

Slide 41 text

NAVIGATION OPTIONS Navigate A B C A A B C navigate

Slide 42

Slide 42 text

NAVIGATION OPTIONS PopUpTo A A B C

Slide 43

Slide 43 text

NAVIGATION OPTIONS PopUpTo A B A B C navigate

Slide 44

Slide 44 text

NAVIGATION OPTIONS PopUpTo A B C A B C navigate

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

NAVIGATION OPTIONS PopUpTo A A A B C

Slide 47

Slide 47 text

NAVIGATION OPTIONS PopUpTo & Inclusive A A B C

Slide 48

Slide 48 text

NAVIGATION OPTIONS PopUpTo & Inclusive A B A B C navigate

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

APPLICATION

Slide 53

Slide 53 text

SCREEN BOTTOM NAVIGATION

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

THANK YOU FOR LISTENING! @alex_zhukovich https://alexzh.com/ alex-zhukovich