Reputation: 3264
I am looking for a Compose variant of setupWithNavController(Toolbar, NavController) to automatically update the up button in an AppBar
whenever the Navigation destination changes.
So far I haven't found anything useful.
Is it considered a bad design in Compose? Or is there some simple way how to achieve the same thing that I am not seeing?
Upvotes: 7
Views: 2571
Reputation: 10125
You can show the Up button like @Antimonit shows, or this simplified version that does not use the custom previousBackStackEntryAsState() function:
@Composable
fun NavigationIcon(navController: NavController) {
val backStackEntry by navController.currentBackStackEntryAsState()
val x = backStackEntry?.destination // Triggers recomposition
if (navController.previousBackStackEntry != null) {
IconButton(onClick = {
navController.navigateUp()
}) {
Icon(Icons.Default.ArrowBack, contentDescription = "Up")
}
}
}
Upvotes: 0
Reputation: 3264
I've hacked up a solution, but I am not satisfied.
androidx.navigation:navigation-compose:1.0.0-alpha08
provides an extension function to observe the current back stack entry.
@Composable
fun NavController.currentBackStackEntryAsState(): State<NavBackStackEntry?>
I've created a similar extension to observe the previous back stack entry
/**
* Gets the previous navigation back stack entry as a [MutableState]. When the given navController
* changes the back stack due to a [NavController.navigate] or [NavController.popBackStack] this
* will trigger a recompose and return the second top entry on the back stack.
*
* @return a mutable state of the previous back stack entry
*/
@Composable
fun NavController.previousBackStackEntryAsState(): State<NavBackStackEntry?> {
val previousNavBackStackEntry = remember { mutableStateOf(previousBackStackEntry) }
// setup the onDestinationChangedListener responsible for detecting when the
// previous back stack entry changes
DisposableEffect(this) {
val callback = NavController.OnDestinationChangedListener { controller, _, _ ->
previousNavBackStackEntry.value = controller.previousBackStackEntry
}
addOnDestinationChangedListener(callback)
// remove the navController on dispose (i.e. when the composable is destroyed)
onDispose {
removeOnDestinationChangedListener(callback)
}
}
return previousNavBackStackEntry
}
and a composable for the back button
@Composable
fun NavigationIcon(navController: NavController): @Composable (() -> Unit)? {
val previousBackStackEntry: NavBackStackEntry? by navController.previousBackStackEntryAsState()
return previousBackStackEntry?.let {
{
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(Icons.Default.ArrowBack, contentDescription = "Up button")
}
}
}
}
I had to return a @Composable (() -> Unit)?
(instead of no return value common to composable functions) because the TopAppBar
uses the nullability to check whether to offset the title by 16dp (without icon) or by 72dp (with icon).
Finally, the content looks something like this
@Composable
fun MainContent() {
val navController: NavHostController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = { Text("Weather") },
navigationIcon = NavigationIcon(navController)
)
},
) {
NavHost(
navController,
startDestination = "list"
) {
composable("list") {
...
}
composable("detail") {
...
}
}
}
}
It might be cleaner to create a custom NavigationTopAppBar
composable and hoist the NavController
out of NavigationIcon
but the idea stays the same. I didn't bother tinkering further.
I've also attempted to automatically update the title according to the current NavGraph destination. Unfortunately, there is not a reliable way to set a label to destinations without extracting quite a big chunk of internal implementation out of androidx.navigation:navigation-compose
library.
Upvotes: 6