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. @alex_zhukovich
    https://alexzh.com/
    Jetpack Compose


    Navigation

    View Slide

  2. ANDROID
    NAVIGATION HISTORY



    INTENT


    FRAGMENT MANAGER


    JETPACK NAVIGATION

    View Slide

  3. Activity based navigation
    API 1

    2008
    API 11

    View Slide

  4. API 1

    Fragment based navigation
    API 11


    2011

    View Slide

  5. API 1

    Navigation Graph

    2019
    INTENT FRAGMENT MANAGER JETPACK NAVIGATION

    View Slide

  6. NAVIGATION


    USE CASES

    View Slide

  7. SCREEN ➔ SCREEN
    UPDATE PART OF THE SCREEN

    View Slide

  8. SCREEN ➔ SCREEN
    UPDATE PART OF THE SCREEN

    View Slide

  9. ACTIVITY

    View Slide

  10. ACTIVITY FRAGMENT

    View Slide

  11. ACTIVITY FRAGMENT
    COMPOSABLE


    FUNCTION

    View Slide

  12. JETPACK COMPOSE

    NAVIGATION



    ANDROID APPS


    ROUTING


    NAVIGATION GRAPHS

    View Slide

  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

    }

    )

    }
    }

    }

    View Slide

  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

    }

    )

    }
    }

    }

    View Slide

  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

    )

    }

    }

    View Slide

  16. ANDROID APPS

    View Slide

  17. EXISTING
    APPS
    JETPACK NAVIGATION COMPOSE VIEW
    FRAGMENTS

    View Slide

  18. NEW
    APPS
    JETPACK COMPOSE
    NAVIGATION
    @COMPOSABLE
    FULL COMPOSABLE

    View Slide

  19. NAVIGATION GRAPH

    View Slide

  20. NavHost
    Destination Destination
    Destination
    NavHost(

    navController,


    startDestination = Screen.Destination1.route

    ) {

    composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) }


    composable(“Basket”) { BasketScreen(...) }


    composable(“Profile”) { Destination1Screen(...) }


    }

    View Slide

  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

    View Slide

  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

    View Slide

  23. NavHost
    NavGraph
    NavGraph
    Destination Destination
    Destination
    Destination

    View Slide

  24. NavHost(


    navController = navController,


    startDestination = "CoffeeDrinks"


    ) {


    composable("CoffeeDrinks") {


    CoffeeDrinksScreen(


    navigateToDetails = {


    navController.navigate("CoffeeDrinkDetails")


    },


    navigateToBasket = {


    navController.navigate(“Basket")


    }


    )


    }


    composable("CoffeeDrinkDetails") {


    CoffeeDrinkDetails()


    }


    composable("Basket") {


    Basket()


    }


    }

    View Slide

  25. NavHost(


    navController = navController,


    startDestination = "CoffeeDrinks"


    ) {


    composable("CoffeeDrinks") {


    CoffeeDrinksScreen(


    navigateToDetails = {


    navController.navigate("CoffeeDrinkDetails")


    },


    navigateToBasket = {


    navController.navigate(“Basket")


    }


    )


    }


    composable("CoffeeDrinkDetails") {


    CoffeeDrinkDetails()


    }


    composable("Basket") {


    Basket()


    }


    }

    View Slide

  26. NavHost(


    navController = navController,


    startDestination = "CoffeeDrinks"


    ) {


    composable("CoffeeDrinks") {


    CoffeeDrinksScreen(


    navigateToDetails = {


    navController.navigate("CoffeeDrinkDetails")


    },


    navigateToBasket = {


    navController.navigate(“Basket")


    }


    )


    }


    composable("CoffeeDrinkDetails") {


    CoffeeDrinkDetails()


    }


    composable("Basket") {


    Basket()


    }


    }

    View Slide

  27. public fun navigate
    (

    route: String,


    builder: NavOptionsBuilder.() -> Uni
    t

    )
    {

    navigate(route, navOptions(builder)
    )

    }

    navigate
    Try to
    f

    View Slide

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

    }

    View Slide

  29. ROUTING

    View Slide

  30. DEMO


    TIME

    View Slide

  31. ROUTING
    NavHost(

    navController = tabsNavController,


    startDestination = NavigationItem.CoffeeDrinks.rout
    e

    )
    {

    composable(“CoffeeDrinks”) { CoffeeDrinksScreen(...) }


    composable(“Basket”) { BasketScreen(...) }


    composable(“Profile”) { Destination1Screen(...) }


    }


    navController.navigate(“CoffeeDrinks”)

    View Slide

  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

    )

    }

    }

    View Slide

  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

    )

    }

    }

    View Slide

  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

    )

    }

    }

    View Slide

  35. DEEP LINKING
    ...
    >


    y

    android:name=".MainActivity
    "

    ...
    >

    >

    ...
    >

    >

    >

    a

    android:host="example.com
    "

    android:scheme="https" /
    >

    >

    >


    AndroidManifest.xml

    View Slide

  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

    View Slide

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

    }
    }

    View Slide

  38. NAVIGATION OPTIONS Navigate
    A
    A B C

    View Slide

  39. NAVIGATION OPTIONS Navigate
    A
    B
    A B C
    navigate

    View Slide

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

    View Slide

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

    View Slide

  42. NAVIGATION OPTIONS PopUpTo
    A
    A B C

    View Slide

  43. NAVIGATION OPTIONS PopUpTo
    A
    B
    A B C
    navigate

    View Slide

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

    View Slide

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


    popUpTo(“A”)


    }

    View Slide

  46. NAVIGATION OPTIONS PopUpTo
    A
    A
    A B C

    View Slide

  47. NAVIGATION OPTIONS PopUpTo & Inclusive
    A
    A B C

    View Slide

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

    View Slide

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

    View Slide

  50. NAVIGATION OPTIONS PopUpTo & Inclusive
    A
    B
    C
    A B C
    navigate(“A”) {


    popUpTo(“A”) { inclusive = true }


    }

    View Slide

  51. NAVIGATION OPTIONS PopUpTo & Inclusive
    A
    A B C
    navigate(“A”) {


    popUpTo(“A”) { inclusive = true }


    }

    View Slide

  52. APPLICATION

    View Slide

  53. SCREEN BOTTOM NAVIGATION

    View Slide

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

    View Slide

  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
    )

    }

    )

    }

    ...
    }
    }

    }

    }

    }

    View Slide

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

    ..
    .

    }

    }

    }

    }

    View Slide

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

    ..
    .

    }

    }

    }

    }

    View Slide

  58. THANK YOU


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

    View Slide