Before Jetpack Compose I was using Navigation Component in the projects in View system world.
Apps had only one activity - toolbar, bottom bar and drawer were added only to this activity once.
Apps could have many screens (fragments) and only top destination fragments were displaying bottom bar and allowed drawer, for other fragments it was hidden.
All of that was handled with Navigation Component like this from the activity:
fun initNavigation() {
val topLevelDestinationFragments = setOf(R.id.homeFragment, R.id.photosFragment)
appBarConfiguration = AppBarConfiguration(
topLevelDestinationFragments,
binding.drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.drawerNavigationView.setupWithNavController(navController)
binding.bottomNavigationView.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
// don't change bars if a dialog fragment
if (destination is FloatingWindow) {
return@addOnDestinationChangedListener
}
// Google solution to hide navigation bars
// https://developer.android.com/guide/navigation/navigation-ui#listen_for_navigation_events
val allowBottomAndDrawerNavigation = destination.id in topLevelDestinationFragments
binding.bottomNavigationView.isVisible = allowBottomAndDrawerNavigation
binding.drawerLayout.setDrawerLockMode(
if (allowBottomAndDrawerNavigation) {
DrawerLayout.LOCK_MODE_UNLOCKED
} else {
DrawerLayout.LOCK_MODE_LOCKED_CLOSED
}
)
binding.toolbar.isVisible = // if we need to hide toolbar for specific fragments
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
Quite easy, no need to add separately toolbar for each fragment and so on (so it was added only to the layout of the activity, it wasn't added to each layout of each fragment)
Navigation Component automatically handles back and menu (for drawer) buttons on the toolbar, automatically switches between them because it knows top destinations
Is there something similar for Compose?
Because I checked official Google sample "JetNews" from CodeLabs git https://github.com/googlecodelabs/android-compose-codelabs/tree/end/AccessibilityCodelab/app/src/main/java/com/example/jetnews/ui
They use compose navigation there but they separately added Scaffold
for each compose screen.
For example "Home" compose screen has it own Scaffold
Scaffold(
scaffoldState = scaffoldState,
topBar = {
val title = stringResource(id = R.string.app_name)
InsetAwareTopAppBar(
title = { Text(text = title) },
navigationIcon = {
IconButton(onClick = { coroutineScope.launch { openDrawer() } }) {
Icon(
painter = painterResource(R.drawable.ic_jetnews_logo),
contentDescription = stringResource(R.string.cd_open_navigation_drawer)
)
}
}
)
}
)
And "Article" compose screen has it own Scaffold
Scaffold(
topBar = {
InsetAwareTopAppBar(
title = {
Text(
text = "Published in: ${post.publication?.name}",
style = MaterialTheme.typography.subtitle2,
color = LocalContentColor.current
)
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
// Step 4: Content descriptions
contentDescription = stringResource(
R.string.cd_navigate_up
)
)
}
}
)
}
)
So basically here we duplicate the code and define manually different logic for navigationIcon
(icon and action) of toolbar
Does it mean that if we have some details screens with back arrow button and without bottom bar then we define a separate Scaffold
and can't just use one Scaffold
for all compose screens of the app?
Or can we implement the same logic as well for all compose screens as we did with Navigation Component in View system? Also setting top destinations, hiding bottom bar and locking drawer for non top destinations.
I guess the correct way is to set top level
Scaffold
(where we define theme and navigation) withAppDrawer
andBottomBar
and add some logic if it should be added to a screen by checking the current navigation route:e.g.:
But with
TopAppBar
it's not that easy. We can't really add it to top levelScaffold
in case when toolbar can have actions for specific screensThat's why each screen additionally defines its
Scaffold
withTopAppBar
configured as needed.Only if an app is very simple and toolbar doesn't have any actions for all screens of the app, just hardcoded titles for all screens, then we can define
TopAppBar
in top levelScaffold
but for more complicated apps each screen should define its own toolbar