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...
A
- it shows A
B
- it shows B
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:
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) },
)
}
}
}
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