Jetpack Compose: navigate to X screen (jump) keeping back-stack history

Viewed 60

Have Activity which holds NavHostController, when activity starts, need to navigate X screen, but pressing back need to navigate to previous screen, not to the start destination (because navigating to X was from startDest).

@OptIn(ExperimentalMaterialNavigationApi::class)
@Composable
private fun NavHostController(
    navController: NavHostController,
    startDest: String = "screen 1"
) {
    NavHost(
        navController = navController,
        startDestination = startDest,
    ) {
        composable("screen 1") {}
        composable("screen 2") {}
        ...
        composable("screen N") {}
}}

EXAMPLE:

have screens like: "screen 1-2-3-4-5..N"

and the "screen 1" is start destination, activity starts and nav-ctrl jumps to screen 5(for example), and the back press need to work like: to 4, 3, 2.. NOT direct 1 (as start destination is 1)

Question: How to set back stack history in nav controller OR How navigate (jump) with keeping back stack order.

1 Answers

To achieve this you will need to use the BackHandler and make some check to see if the previous back stack route is the screen that should be the destination when using the back button. For example:

@Composable
fun ScreenOne(
    nextScreen: () -> Unit
) {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "ScreenOne")

        Button(onClick = nextScreen) {
            Text(text = "Next")
        }
    }
}
@Composable
fun ScreenTwo(
    back: () -> Unit,
    nextScreen: () -> Unit
) {
    // to intercept the click on the back button of the android navigation bar
    BackHandler(onBack = back)

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "ScreenTwo")

        Button(onClick = back) {
            Text(text = "Back")
        }

        Button(onClick = nextScreen) {
            Text(text = "Next")
        }
    }
}
@Composable
fun ScreenThree(
    back: () -> Unit
) {
    // to intercept the click on the back button of the android navigation bar
    BackHandler(onBack = back)

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "ScreenThree")

        Button(onClick = back) {
            Text(text = "Back")
        }
    }
}

ScreenOne just have the option to navigate to ScreenTwo.
ScreenTwo have the option to navigate (back) to ScreenOne and navigate to ScreenThree.
ScreenThree just have the option to navigate (back) to ScreenTwo.

Extension to navigate back:

fun NavHostController.navigateBack(
    targetRoute: String,
    currentRoute: String
) {
    val previousRoute = previousBackStackEntry?.destination?.route ?: "null"

    // if the previous route is what we want, just go back
    if (previousRoute == targetRoute) popBackStack()
    // otherwise, we do the navigation explicitly
    else navigate(route = targetRoute) {
        // remove the entire backstack up to this this route, including herself
        popUpTo(route = currentRoute) { inclusive = true }
        launchSingleTop = true
    }
}

NavHost:

NavHost(
    modifier = Modifier.fillMaxSize(),
    navController = navController,
    startDestination = "screen_three"
) {
    composable(route = "screen_one") {
        ScreenOne(
            nextScreen = {
                navController.navigate(route = "screen_two") {
                    launchSingleTop = true
                }
            }
        )
    }

    composable(route = "screen_two") {
        ScreenTwo(
            back = {
                navController.navigateBack(
                    targetRoute = "screen_one",
                    currentRoute = "screen_two"
                )
            },
            nextScreen = {
                navController.navigate(route = "screen_three") {
                    launchSingleTop = true
                }
            }
        )
    }

    composable(route = "screen_three") {
        ScreenThree(
            back = {
                navController.navigateBack(
                    targetRoute = "screen_two",
                    currentRoute = "screen_three"
                )
            }
        )
    }
}
Related