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 full-size slide

  2. ANDROID
    NAVIGATION HISTORY



    INTENT


    FRAGMENT MANAGER


    JETPACK NAVIGATION

    View full-size slide

  3. Activity based navigation
    API 1

    2008
    API 11

    View full-size slide

  4. API 1

    Fragment based navigation
    API 11


    2011

    View full-size slide

  5. API 1

    Navigation Graph

    2019
    INTENT FRAGMENT MANAGER JETPACK NAVIGATION

    View full-size slide

  6. NAVIGATION


    USE CASES

    View full-size slide

  7. SCREEN ➔ SCREEN
    UPDATE PART OF THE SCREEN

    View full-size slide

  8. SCREEN ➔ SCREEN
    UPDATE PART OF THE SCREEN

    View full-size slide

  9. ACTIVITY FRAGMENT

    View full-size slide

  10. ACTIVITY FRAGMENT
    COMPOSABLE


    FUNCTION

    View full-size slide

  11. JETPACK COMPOSE

    NAVIGATION



    ANDROID APPS


    ROUTING


    NAVIGATION GRAPHS

    View full-size slide

  12. 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 full-size 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 full-size slide

  14. @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 full-size slide

  15. ANDROID APPS

    View full-size slide

  16. EXISTING
    APPS
    JETPACK NAVIGATION COMPOSE VIEW
    FRAGMENTS

    View full-size slide

  17. NEW
    APPS
    JETPACK COMPOSE
    NAVIGATION
    @COMPOSABLE
    FULL COMPOSABLE

    View full-size slide

  18. NAVIGATION GRAPH

    View full-size slide

  19. NavHost
    Destination Destination
    Destination
    NavHost(

    navController,


    startDestination = Screen.Destination1.route

    ) {

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


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


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


    }

    View full-size slide

  20. @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 full-size 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 full-size slide

  22. NavHost
    NavGraph
    NavGraph
    Destination Destination
    Destination
    Destination

    View full-size slide

  23. NavHost(


    navController = navController,


    startDestination = "CoffeeDrinks"


    ) {


    composable("CoffeeDrinks") {


    CoffeeDrinksScreen(


    navigateToDetails = {


    navController.navigate("CoffeeDrinkDetails")


    },


    navigateToBasket = {


    navController.navigate(“Basket")


    }


    )


    }


    composable("CoffeeDrinkDetails") {


    CoffeeDrinkDetails()


    }


    composable("Basket") {


    Basket()


    }


    }

    View full-size 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 full-size 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 full-size slide

  26. public fun navigate
    (

    route: String,


    builder: NavOptionsBuilder.() -> Uni
    t

    )
    {

    navigate(route, navOptions(builder)
    )

    }

    navigate
    Try to
    f

    View full-size slide

  27. 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 full-size slide

  28. ROUTING
    NavHost(

    navController = tabsNavController,


    startDestination = NavigationItem.CoffeeDrinks.rout
    e

    )
    {

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


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


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


    }


    navController.navigate(“CoffeeDrinks”)

    View full-size slide

  29. 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 full-size slide

  30. 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 full-size slide

  31. 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 full-size slide

  32. DEEP LINKING
    ...
    >


    y

    android:name=".MainActivity
    "

    ...
    >

    >

    ...
    >

    >

    >

    a

    android:host="example.com
    "

    android:scheme="https" /
    >

    >

    >


    AndroidManifest.xml

    View full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. NAVIGATION OPTIONS Navigate
    A
    A B C

    View full-size slide

  36. NAVIGATION OPTIONS Navigate
    A
    B
    A B C
    navigate

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. NAVIGATION OPTIONS PopUpTo
    A
    A B C

    View full-size slide

  40. NAVIGATION OPTIONS PopUpTo
    A
    B
    A B C
    navigate

    View full-size slide

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

    View full-size slide

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


    popUpTo(“A”)


    }

    View full-size slide

  43. NAVIGATION OPTIONS PopUpTo
    A
    A
    A B C

    View full-size slide

  44. NAVIGATION OPTIONS PopUpTo & Inclusive
    A
    A B C

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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


    popUpTo(“A”) { inclusive = true }


    }

    View full-size slide

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


    popUpTo(“A”) { inclusive = true }


    }

    View full-size slide

  49. SCREEN BOTTOM NAVIGATION

    View full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. THANK YOU


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

    View full-size slide