Search code examples
androidkotlinnavigationandroid-jetpack-compose

How can we hide BottomAppBar (with navigation include) when navigate to composable?


I have a problem.

The problem is that i don't know how to hide bottom app bar when i'm navigating to a "add question" screen.

I need your help please.

This is MyScreen with the bottom app bar

@Composable
fun Navigation() {
    val navController = rememberNavController()
    val items = listOf(Screen.Home, Screen.Search, Screen.Notifications, Screen.Profil)

    Scaffold(
        bottomBar = {
            bottomAppNavigation(navController = navController, items)

        }
    ) {
        Box(modifier = Modifier.padding(it)) {
            ScreenController(navController = navController)
        }

    }
}

And this is My controller with navHost

@ExperimentalComposeUiApi
@Composable
fun ScreenController(navController: NavHostController) {
    NavHost(navController = navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            HomeScreen(navController)
        }
        composable(Screen.Search.route) {
            SearchScreen(navController, it)
        }
        composable(Screen.Notifications.route) {

        }
        composable(Screen.Profil.route) {
            user_profil()
        }
        composable("Ask_question") {
            AskScreen(navController)
        }
    }
}

The problem i think it's because that's like activity and fragment, i have a box where the composable screen goes, and all my pages are in him.


Solution

  • I recommend you use AnimatedVisibility for BottomNavigation widget, im my opinion it's clearest way for compose.

    1. You should use remeberSaveable to store state of BottomBar:
    // State of bottomBar, set state to false, if current page route is "car_details"
    val bottomBarState = rememberSaveable { (mutableStateOf(true)) }
    
    1. In composable function we used when for control state of BottomBar, below we set bottomBarState to true, if we would like to show BottomBar, otherwise we set bottomBarState to false:
    val navController = rememberNavController()
    
    // Subscribe to navBackStackEntry, required to get current route
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    
    // Control BottomBar
    when (navBackStackEntry?.destination?.route) {
        "cars" -> {
            // Show BottomBar
            bottomBarState.value = true
        }
        "bikes" -> {
            // Show BottomBar
            bottomBarState.value = true
        }
        "settings" -> {
            // Show BottomBar
            bottomBarState.value = true
        }
        "car_details" -> {
            // Hide BottomBar
            bottomBarState.value = false
        }
    }
    
    Scaffold(
        bottomBar = {
            BottomBar(
                navController = navController,
                bottomBarState = bottomBarState
            )
        },
        content = {
            NavHost(
                navController = navController,
                startDestination = NavigationItem.Cars.route,
            ) {
                composable(NavigationItem.Cars.route) {
                    CarsScreen(
                        navController = navController,
                    )
                }
                composable(NavigationItem.Bikes.route) {
                    BikesScreen(
                        navController = navController
                    )
                }
                composable(NavigationItem.Settings.route) {
                    SettingsScreen(
                        navController = navController,
                    )
                }
                composable(NavigationItem.CarDetails.route) {
                    CarDetailsScreen(
                        navController = navController,
                    )
                }
            }
        }
    )
    
    1. Put BottomNavigation inside AnimatedVisibility, set visible value from bottomBarState and set enter and exit animation, in my case I use slideInVertically for enter animation and slideOutVertically for exit animation:
    AnimatedVisibility(
            visible = bottomBarState.value,
            enter = slideInVertically(initialOffsetY = { it }),
            exit = slideOutVertically(targetOffsetY = { it }),
            content = {
                BottomNavigation {
                    val navBackStackEntry by navController.currentBackStackEntryAsState()
                    val currentRoute = navBackStackEntry?.destination?.route
    
                    items.forEach { item ->
                        BottomNavigationItem(
                            icon = {
                                Icon(
                                    painter = painterResource(id = item.icon),
                                    contentDescription = item.title
                                )
                            },
                            label = { Text(text = item.title) },
                            selected = currentRoute == item.route,
                            onClick = {
                                navController.navigate(item.route) {
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        saveState = true
                                    }
                                    launchSingleTop = true
                                    restoreState = true
                                }
                            }
                        )
                    }
                }
            }
        )
    

    Full code of MainActivity:

    package codes.andreirozov.bottombaranimation
    
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.animation.AnimatedVisibility
    import androidx.compose.animation.ExperimentalAnimationApi
    import androidx.compose.animation.slideInVertically
    import androidx.compose.animation.slideOutVertically
    import androidx.compose.material.*
    import androidx.compose.runtime.*
    import androidx.compose.runtime.saveable.rememberSaveable
    import androidx.compose.ui.res.painterResource
    import androidx.navigation.NavController
    import androidx.navigation.NavGraph.Companion.findStartDestination
    import androidx.navigation.compose.NavHost
    import androidx.navigation.compose.composable
    import androidx.navigation.compose.currentBackStackEntryAsState
    import androidx.navigation.compose.rememberNavController
    import codes.andreirozov.bottombaranimation.screens.BikesScreen
    import codes.andreirozov.bottombaranimation.screens.CarDetailsScreen
    import codes.andreirozov.bottombaranimation.screens.CarsScreen
    import codes.andreirozov.bottombaranimation.screens.SettingsScreen
    import codes.andreirozov.bottombaranimation.ui.theme.BottomBarAnimationTheme
    
    @ExperimentalAnimationApi
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                BottomBarAnimationApp()
            }
        }
    }
    
    @ExperimentalAnimationApi
    @Composable
    fun BottomBarAnimationApp() {
    
        // State of bottomBar, set state to false, if current page route is "car_details"
        val bottomBarState = rememberSaveable { (mutableStateOf(true)) }
    
        BottomBarAnimationTheme {
            val navController = rememberNavController()
    
            // Subscribe to navBackStackEntry, required to get current route
            val navBackStackEntry by navController.currentBackStackEntryAsState()
    
            // Control BottomBar
            when (navBackStackEntry?.destination?.route) {
                "cars" -> {
                    // Show BottomBar
                    bottomBarState.value = true
                }
                "bikes" -> {
                    // Show BottomBar
                    bottomBarState.value = true
                }
                "settings" -> {
                    // Show BottomBar
                    bottomBarState.value = true
                }
                "car_details" -> {
                    // Hide BottomBar
                    bottomBarState.value = false
                }
            }
    
            Scaffold(
                bottomBar = {
                    BottomBar(
                        navController = navController,
                        bottomBarState = bottomBarState
                    )
                },
                content = {
                    NavHost(
                        navController = navController,
                        startDestination = NavigationItem.Cars.route,
                    ) {
                        composable(NavigationItem.Cars.route) {
                            CarsScreen(
                                navController = navController,
                            )
                        }
                        composable(NavigationItem.Bikes.route) {
                            BikesScreen(
                                navController = navController
                            )
                        }
                        composable(NavigationItem.Settings.route) {
                            SettingsScreen(
                                navController = navController,
                            )
                        }
                        composable(NavigationItem.CarDetails.route) {
                            CarDetailsScreen(
                                navController = navController,
                            )
                        }
                    }
                }
            )
        }
    }
    
    @ExperimentalAnimationApi
    @Composable
    fun BottomBar(navController: NavController, bottomBarState: MutableState<Boolean>) {
        val items = listOf(
            NavigationItem.Cars,
            NavigationItem.Bikes,
            NavigationItem.Settings
        )
    
        AnimatedVisibility(
            visible = bottomBarState.value,
            enter = slideInVertically(initialOffsetY = { it }),
            exit = slideOutVertically(targetOffsetY = { it }),
            content = {
                BottomNavigation {
                    val navBackStackEntry by navController.currentBackStackEntryAsState()
                    val currentRoute = navBackStackEntry?.destination?.route
    
                    items.forEach { item ->
                        BottomNavigationItem(
                            icon = {
                                Icon(
                                    painter = painterResource(id = item.icon),
                                    contentDescription = item.title
                                )
                            },
                            label = { Text(text = item.title) },
                            selected = currentRoute == item.route,
                            onClick = {
                                navController.navigate(item.route) {
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        saveState = true
                                    }
                                    launchSingleTop = true
                                    restoreState = true
                                }
                            }
                        )
                    }
                }
            }
        )
    }
    

    Result:

    BottomBar animation

    Don't forget to use @ExperimentalAnimationApi annotation for compose functions.

    Update: with Compose version 1.1.0 and above @ExperimentalAnimationApi not required.

    22.02.2022 Update: I made some research, and update point 2. Now we use when for control bottomBarState.

    Full code available on gitHub: https://github.com/AndreiRoze/BottomBarAnimation

    Examples of animations available in the official documentation: https://developer.android.com/jetpack/compose/animation/composables-modifiers