android - How to capture BackButton press and gesture before NavigationHost does - Stack Overflow

admin2025-04-29  2

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

Share Improve this question asked Jan 6 at 22:30 V.LorzV.Lorz 3952 silver badges13 bronze badges 1
  • You have to use BackHandler in the NavigationDrawer code. So that It can only handle back press in that module only – Chirag Thummar Commented Jan 7 at 4:58
Add a comment  | 

1 Answer 1

Reset to default 0

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)
    }
}
转载请注明原文地址:http://anycun.com/QandA/1745940997a91421.html