Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle Android Compose BottomBar Navigation mixed with arguments

I have an Android app using compose navigation. Navigating between its three screens - Home, Calendar, More - is done via a bottom bar:

// onBottomBarItemClick from https://developer.android.com/jetpack/compose/navigation#bottom-nav: 
navController.navigate(destination) {
    popUpTo("HOME") {
        saveState = true
    }
    launchSingleTop = true
    restoreState = true
}

Up until now everything is working as expected.

However, i sometimes want to pass an argument from Home to Calendar - see screenshot. This is where things start to break apart.

HomeScreen(
    onNavigateToCalendar = { argument ->
        navController.navigate("CALENDAR?ARG=$argument") {
            popUpTo("HOME") {
                saveState = true
            }
            launchSingleTop = true
            restoreState = false
        }
    }
)

If i now do the following...

  • Start the app fresh - i'm at home
  • Navigate to Calendar with argument A - it shows A
  • Navigate to Home via BottomBar
  • Navigate to Calendar with argument B - it shows B
  • Navigate to More
  • Navigate to Calendar via BottomBar - it shows A not B - which is weird.

I believe i have tried every possible combination of saveState / launchSingleTop / restoreState, but all of them had some issues. Can someone help me please? I'm loking for a solution where:

  • Calendar will always display an argument when explicitly provided
  • Calendar will display none or the last argument, when no argument is provided
  • "Back" works correctly: Navigating back from Calendar / More should lead to Home
  • State (e.g. scroll state) is kept as you'd expect

enter image description here


Minimal Example:

@Composable
fun MainScreen() {
    val navController = rememberNavController()

    Scaffold(
        content = { paddingValues ->
            NavHost(
                navController = navController,
                startDestination = "HOME",
                modifier = Modifier.padding(paddingValues)
            ) {
                composable("HOME") {
                    HomeScreen(
                        onNavigateToCalendar = { argument ->
                            navController.navigate("CALENDAR?ARG=$argument") {
                                popUpTo("HOME") {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = false
                            }
                        }
                    )
                }

                composable("CALENDAR?ARG={ARG}") {
                    CalendarScreen(it.arguments?.getString("ARG"))
                }

                composable("MORE") {
                    MoreScreen()
                }
            }
        },
        bottomBar = {
            MyBottomBar(
                onClick = { destination ->
                    navController.navigate(destination) {
                        popUpTo("HOME") {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    )
}

@Composable
private fun HomeScreen(
    onNavigateToCalendar: (argument: String) -> Unit
) {
    Column {
        Text("HOME")

        Button(
            onClick = { onNavigateToCalendar("A") },
            content = { Text("Navigate to CALENDAR with argument = A") },
        )

        Button(
            onClick = { onNavigateToCalendar("B") },
            content = { Text("Navigate to CALENDAR with argument = B") },
        )
    }
}

@Composable
private fun CalendarScreen(argument: String?) {
    Text("CALENDAR with argument = $argument")
}

@Composable
private fun MoreScreen() {
    Text("MORE")
}

@Composable
private fun MyBottomBar(
    onClick: (destination: String) -> Unit
) {
    BottomAppBar {
        listOf("HOME", "CALENDAR", "MORE").forEach { destination ->
            BottomNavigationItem(
                selected = false, // TODO - not important for now
                icon = {},
                label = { Text(destination) },
                onClick = { onClick(destination) },
            )
        }
    }
}
like image 444
m.reiter Avatar asked Oct 21 '25 04:10

m.reiter


1 Answers

When navigating to your Calendar screen sending a specific parameter (either A or B, via the onClick listener) you need to clear the backStack of your route so the new state can be saved correctly. Like this:

onNavigateToCalendar = { argument ->
   navController.clearBackStack("CALENDAR?ARG={ARG}")
   navController.navigate("CALENDAR?ARG=$argument") {
        ...
   }
}

For more details you can check their official comment here: https://issuetracker.google.com/issues/294408574

To me it is implicit that I want to override the previous value by using savingState=true and restoreState=false, but seems they don't save any state if you didn't restored the previous one... so you need to clear the backstack manually before going to your screen

like image 150
Rodrigo Soria Avatar answered Oct 23 '25 18:10

Rodrigo Soria



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!