For a Jetpack Compose Android application that requires to navigate through a number of pages, I'm combining a ModalNavigationDrawer
with a NavigationHost
.
I'm new to Jetpack Compose so, perhaps, this is not the way to go.
A clickable hamburger icon placed in a TopAppBar
allows for opening a ModalNavigationDrawer
when clicked.
I'm using the BackHandler
composable for receiving back button presses, see code below, so I can close the drawer if it is open. That's what I want to achieve.
It works OK, the lambda in the BackHandler
is called, when the navigator's backstack is empty, but the back navigation for the NavigationHost
is called when its backstack is not empty.
How can I do for capturing and processing back button presses before the NavigationHost
handles them?
This is the simplified code for onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
/* etc. */
setContent {
/* viewModel, etc. */
AppTheme {
Scaffold { paddingValues ->
MainActivityScreen(
viewModel = viewModel,
modifier = Modifier.padding(paddingValues = paddingValues)
)
}
}
}
}
And MainActivityScreen
compose is just like:
@Composable
fun MainActivityScreen(
viewModel: HomeScreenViewModel,
modifier: Modifier = Modifier
) {
val navHostController = rememberNavController()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
state = rememberTopAppBarState()
)
BackHandler(enabled = viewModel.isDrawerOpen) {
viewModel.onHomeScreenAction(action = HomeScreenAction.CloseDrawer)
}
ModalNavigationDrawer(
drawerState = viewModel.drawerState,
drawerContent = {
ModalDrawerSheet {
LazyColumn {
// drawerHeader() composes a header for the drawer
drawerHeader()
items(Screen.screens) { screen ->
val selected = navHostController.currentBackStackEntry?.destination?.route == screen.route
DrawerScreenItem(
screen = screen,
selected = selected,
) {
// `viewModel.onHomeScreenAction`, eventually, emits target routes using `viewModel.navigationTargetFlow`.
viewModel.onHomeScreenAction(HomeScreenAction.SelectScreen(screen = screen))
}
}
}
}
}
) {
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = /* ..top bar.. */
) { innerPadding ->
NavigationHost(
navHostController = navHostController,
innerPadding = innerPadding
)
LaunchedEffect("navigation") {
// viewModel.navigationTargetFlow returns a flow of `data class Screen(val target: String) ...etc...`,
viewModel.navigationTargetFlow.onEach {
navHostController.navigate(it.target)
}.launchIn(this)
}
}
}
}
BR
For a Jetpack Compose Android application that requires to navigate through a number of pages, I'm combining a ModalNavigationDrawer
with a NavigationHost
.
I'm new to Jetpack Compose so, perhaps, this is not the way to go.
A clickable hamburger icon placed in a TopAppBar
allows for opening a ModalNavigationDrawer
when clicked.
I'm using the BackHandler
composable for receiving back button presses, see code below, so I can close the drawer if it is open. That's what I want to achieve.
It works OK, the lambda in the BackHandler
is called, when the navigator's backstack is empty, but the back navigation for the NavigationHost
is called when its backstack is not empty.
How can I do for capturing and processing back button presses before the NavigationHost
handles them?
This is the simplified code for onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
/* etc. */
setContent {
/* viewModel, etc. */
AppTheme {
Scaffold { paddingValues ->
MainActivityScreen(
viewModel = viewModel,
modifier = Modifier.padding(paddingValues = paddingValues)
)
}
}
}
}
And MainActivityScreen
compose is just like:
@Composable
fun MainActivityScreen(
viewModel: HomeScreenViewModel,
modifier: Modifier = Modifier
) {
val navHostController = rememberNavController()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
state = rememberTopAppBarState()
)
BackHandler(enabled = viewModel.isDrawerOpen) {
viewModel.onHomeScreenAction(action = HomeScreenAction.CloseDrawer)
}
ModalNavigationDrawer(
drawerState = viewModel.drawerState,
drawerContent = {
ModalDrawerSheet {
LazyColumn {
// drawerHeader() composes a header for the drawer
drawerHeader()
items(Screen.screens) { screen ->
val selected = navHostController.currentBackStackEntry?.destination?.route == screen.route
DrawerScreenItem(
screen = screen,
selected = selected,
) {
// `viewModel.onHomeScreenAction`, eventually, emits target routes using `viewModel.navigationTargetFlow`.
viewModel.onHomeScreenAction(HomeScreenAction.SelectScreen(screen = screen))
}
}
}
}
}
) {
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = /* ..top bar.. */
) { innerPadding ->
NavigationHost(
navHostController = navHostController,
innerPadding = innerPadding
)
LaunchedEffect("navigation") {
// viewModel.navigationTargetFlow returns a flow of `data class Screen(val target: String) ...etc...`,
viewModel.navigationTargetFlow.onEach {
navHostController.navigate(it.target)
}.launchIn(this)
}
}
}
}
BR
I had already tried doing as Chirag Thummar comments, but it didn't work the way I tried, but I realised the documentation on custom back navigation says "...the callback added last is the first given a chance to handle the Back button event". That was the reason why the NavigationHost
always had the chance to receive the event first. The solution was to place the Backhandler
after the NavigationHost
, like below.
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = /* ..top bar.. */
) { innerPadding ->
NavigationHost(
navHostController = navHostController,
innerPadding = innerPadding
)
// Here....
BackHandler(enabled = viewModel.isDrawerOpen) {
viewModel.onHomeScreenAction(action = HomeScreenAction.CloseDrawer)
}
LaunchedEffect("navigation") {
// viewModel.navigationTargetFlow returns a flow of `data class Screen(val target: String) ...etc...`,
viewModel.navigationTargetFlow.onEach {
navHostController.navigate(it.target)
}.launchIn(this)
}
}
BackHandler
in theNavigationDrawer
code. So that It can only handle back press in that module only – Chirag Thummar Commented Jan 7 at 4:58