2

I'm building a Jetpack Compose app that uses a bottom NavigationBar to switch between destinations. Each destination is implemented as a nested graph, and there's also a shared TopAppBar. I'm following the type-safe navigation approach shown in this official video.

Desired navigation schema:

├── AGraph
│   ├── A1Screen
│   └── A2Screen
└── BGraph
    ├── B1Screen
    └── B2Screen

Demonstration code:

@Serializable
object AGraph

@Serializable
object A1

@Serializable
object A2

@Serializable
object BGraph

@Serializable
object B1

@Serializable
object B2

data class NavBarDestination<T : Any>(
    val route: T,
    val icon: ImageVector,
    val label: String,
)

val navBarDestinations = listOf(
    NavBarDestination(
        route = AGraph,
        icon = Icons.Default.Call,
        label = "A",
    ),
    NavBarDestination(
        route = BGraph,
        icon = Icons.Default.Settings,
        label = "B",
    ),
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OctopusApp() {
    val navController = rememberNavController()
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination

    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(stringResource(R.string.app_name))
                },
                navigationIcon = {
                    val canNavigateUp = navController.previousBackStackEntry != null
                    if (canNavigateUp) {
                        IconButton(onClick = navController::navigateUp) {
                            Icon(
                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,
                                contentDescription = stringResource(R.string.up_button),
                            )
                        }
                    }
                },
            )
        },
        bottomBar = {
            NavigationBar {
                navBarDestinations.forEach { destination ->
                    NavigationBarItem(
                        selected = currentDestination?.hierarchy?.any {
                            it.hasRoute(destination.route::class)
                        } == true,
                        onClick = {
                            navController.navigate(destination.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        },
                        icon = {
                            Icon(
                                imageVector = destination.icon,
                                contentDescription = destination.label,
                            )
                        },
                        label = { Text(destination.label) },
                    )
                }
            }
        },
    ) { innerPadding ->
        NavHost(
            navController,
            startDestination = AGraph,
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding),
        ) {
            navigation<AGraph>(startDestination = A1) {
                composable<A1> {
                    A1Screen(onClick = { navController.navigate(A2) })
                }
                composable<A2> {
                    A2Screen()
                }
            }
            navigation<BGraph>(startDestination = B1) {
                composable<B1> {
                    B1Screen(onClick = { navController.navigate(B2) })
                }
                composable<B2> {
                    B2Screen()
                }
            }
        }
    }
}

Problem

When I tap between items in the bottom navigation bar, the back stack is actually grows by exactly one destination (even with that popUpTo action in NavigationBarItem onClick callback). As a result, the case of navController.previousBackStackEntry != null is fired and the Up button appears in the TopAppBar, which is not the desired behavior for primary destinations.

What I want instead:

TopAppBar should not show the Up button when switching top level tabs — unless deep inside a section.

Is there a canonical way to implement this behavior? Do I really need to implement custom multiple back stacks (then why do we need these nested navigation graphs at all?)?

0

1 Answer 1

2

Update the argument of the popUpTo() function in your NavigationBarItem's onClick parameter:

navController.navigate(destination.route) { 
    popUpTo(0) { 
        saveState = true 
    } 
    launchSingleTop = true 
    restoreState = true 
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.