Search code examples
androidandroid-jetpack-composeandroid-jetpacknavigationpopbackstack

BackHandler does not work the first time JetPack Compose


I'm trying to override the back button so that when pressed (when I'm on some ModalNavigationDrawer screen) it shows the bottomNavigation and also changes the IconButton.

When it happens by clicking on the icon, everything works as it should, but when it happens by system click, then first there is a kind of transition to the screen, and then only the bottomNavigation and also changes the IconButton is shown.

Video of screen

The code:

package com.example.bussiness.navigation

import android.annotation.SuppressLint
import android.content.ContentValues.TAG
import android.nfc.Tag
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.bussiness.screens.bottom_screens.ExpansesScreen
import com.example.bussiness.screens.bottom_screens.HomeScreen
import com.example.bussiness.screens.Screens
import com.example.bussiness.screens.bottom_screens.SellingScreen
import com.example.bussiness.screens.bottom_screens.StatisticsScreen
import com.example.bussiness.screens.drawer_screens.AboutScreen
import com.example.bussiness.screens.drawer_screens.ExpansesTemplatesScreen
import com.example.bussiness.screens.drawer_screens.FAQScreen
import com.example.bussiness.screens.drawer_screens.ProductScreen
import com.example.bussiness.screens.drawer_screens.SettingsScreen
import com.example.bussiness.screens.drawer_screens.SupportScreen
import kotlinx.coroutines.android.awaitFrame
import kotlinx.coroutines.launch

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavigationSheet() {
    val navigationController = rememberNavController()
    val coroutineScope = rememberCoroutineScope()
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    var selectedItemIndex by rememberSaveable { mutableStateOf(0) }
    var selectedItemIndexDrawer by rememberSaveable { mutableStateOf(-1) }
    var bottomNavigationVisibility by remember {
        mutableStateOf(true)
    }

    var popUpped by remember {
        mutableStateOf(false)
    }

    fun popBack() {
        Log.d("---------------------", "Pop backed")
        selectedItemIndexDrawer = -1
        bottomNavigationVisibility = true
        navigationController.navigateUp()
    }

    BackHandler(popUpped) {
        popUpped = false
        coroutineScope.launch { popBack() }
    }
    

    ModalNavigationDrawer(
        drawerState = drawerState,
        gesturesEnabled = true,
        drawerContent = {
            ModalDrawerSheet {
                Spacer(modifier = Modifier.height(16.dp))
                drawer_items.forEachIndexed { index, item ->
                    NavigationDrawerItem(
                        label = {
                            Text(text = item.title)
                        },
                        selected = index == selectedItemIndexDrawer,
                        onClick = {
                            navigationController.navigate(item.screen) {
                                popUpTo(bottom_items[selectedItemIndex].screen) {
                                    inclusive = false
                                    saveState = false
                                    }
                            }
                            selectedItemIndexDrawer = index
                            coroutineScope.launch {
                                drawerState.close()
                            }
                            bottomNavigationVisibility = false
                            popUpped = true
                        },
                        icon = {
                            Icon(
                                imageVector = if (index == selectedItemIndexDrawer) {
                                    item.selectedIcon
                                } else item.unselectedIcon,
                                contentDescription = item.title
                            )
                        },
                        modifier = Modifier
                            .padding(NavigationDrawerItemDefaults.ItemPadding)
                    )
                }
            }
        },
    ) {
        Scaffold (
            topBar = {
                val scope = rememberCoroutineScope()
                TopAppBar(title = { Text(text = "Main screen") },
                    colors = TopAppBarDefaults.topAppBarColors(),
                    navigationIcon = {
                        if (selectedItemIndexDrawer == -1) {
                            IconButton(
                                onClick = { scope.launch { drawerState.open() } }
                            ) {
                                Icon(imageVector = Icons.Outlined.Menu, contentDescription = "Menu")
                            }
                        }
                        else {
                            IconButton(
                                onClick = { scope.launch { popBack() } }
                            ) {
                                Icon(imageVector = Icons.Outlined.ArrowBack, contentDescription = "Back")
                            }
                        }
                    }
                )
            },
            bottomBar = {
                if (bottomNavigationVisibility) {
                    NavigationBar {
                        bottom_items.forEachIndexed { index, item ->
                            NavigationBarItem(
                                selected = selectedItemIndex == index,
                                onClick = {
                                    selectedItemIndex = index
                                    navigationController.navigate(item.screen) {
                                        popUpTo(Screens.Home.route) {
                                            inclusive = false
                                            saveState = true
                                        }
                                    }
                                },
                                label = {
                                    Text(text = item.title)
                                },
                                alwaysShowLabel = false,
                                icon = {
                                    Icon(
                                        imageVector = if (index == selectedItemIndex) {
                                            item.selectedIcon
                                        } else item.unselectedIcon,
                                        contentDescription = item.title
                                    )
                                }
                            )
                        }
                    }
                }
            }
        ) { padding ->
            NavHost(navController = navigationController, startDestination = Screens.Home.route) {

                composable(Screens.Home.route) { HomeScreen(navController = navigationController, padding) }
                composable(Screens.Selling.route) { SellingScreen(navController = navigationController, padding) }
                composable(Screens.Expanses.route) { ExpansesScreen(navController = navigationController, padding) }
                composable(Screens.Statistics.route) { StatisticsScreen(navController = navigationController, padding) }
                composable(Screens.Products.route) { ProductScreen(navController = navigationController, padding) }
                composable(Screens.Expanses_Templates.route) { ExpansesTemplatesScreen(navController = navigationController, padding) }
                composable(Screens.Settings.route) { SettingsScreen(navController = navigationController, padding) }
                composable(Screens.FAQ.route) { FAQScreen(navController = navigationController, padding) }
                composable(Screens.About.route) { AboutScreen(navController = navigationController, padding) }
                composable(Screens.Support.route) { SupportScreen(navController = navigationController, padding) }

            }
        }
    }
}

I tried the OnBackPressedDispatcherOwner, it has the same effect.

I would be very happy for any help, even that which does not directly relate to this problem, thanks in advance


Solution

  • Read this comment: issue

    So, you should put your BackHandler below NavHost like this:

            { padding ->
                NavHost(navController = navigationController, startDestination = Screens.Home.route) {
    
                    composable(Screens.Home.route) { HomeScreen(navController = navigationController, padding) }
                    composable(Screens.Selling.route) { SellingScreen(navController = navigationController, padding) }
                    composable(Screens.Expanses.route) { ExpansesScreen(navController = navigationController, padding) }
                    composable(Screens.Statistics.route) { StatisticsScreen(navController = navigationController, padding) }
                    composable(Screens.Products.route) { ProductScreen(navController = navigationController, padding) }
                    composable(Screens.Expanses_Templates.route) { ExpansesTemplatesScreen(navController = navigationController, padding) }
                    composable(Screens.Settings.route) { SettingsScreen(navController = navigationController, padding) }
                    composable(Screens.FAQ.route) { FAQScreen(navController = navigationController, padding) }
                    composable(Screens.About.route) { AboutScreen(navController = navigationController, padding) }
                    composable(Screens.Support.route) { SupportScreen(navController = navigationController, padding) }
    
                }
                BackHandler(popUpped) {
                    popUpped = false
                    coroutineScope.launch { popBack() }
                }
            }
    

    Hope this helps you