How to hide bottom bar in Jetpack Compose when using Accompanists` Navigation Animation

Viewed 2748

Situation

I'm writing a pretty simple app using Kotlin & Android Jetpack Compose

I have a scaffold containing my navHost and a bottomBar. I can use that bottomBar to navigate between three main screens. One of those main screens has a detail screen, which should not show a bottomBar.


My Code

So far, this was a piece of cake:

// MainActivitys onCreate

setContent {
    val navController = rememberAnimatedNavController()
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route?.substringBeforeLast("/")

    BottomBarNavTestTheme {
        Scaffold(
            bottomBar = {
                if (currentRoute?.substringBeforeLast("/") == Screen.Detail.route) {
                    MyBottomNavigation(
                        navController,
                        currentRoute,
                        listOf(Screen.Dashboard, Screen.Map, Screen.Events) // main screens
                    )
                }
            }
        ) { innerPadding ->
            NavHost( // to be replaced by AnimatedNavHost
                navController = navController,
                startDestination = Screen.Dashboard.route,
                modifier = Modifier.padding(innerPadding)
            ) {
                composable(Screen.Dashboard.route) { DashboardScreen() }
                composable(Screen.Map.route) { MapScreen { navController.navigate(Screen.Detail.route) } }
                composable(Screen.Events.route) { EventsScreen() }
                composable(Screen.Detail.route) { MapDetailScreen() }
            }
        }
    }
}

Problem:

I would like transitions between my screens, so i'm using Accompanists Navigation Animation: Just replace NavHost with AnimatedNavHost.

When navigating from mainScreen to detailScreen there is a strange effect:

  1. the bottomBar hides
  2. the main screen resizes: (see the bottom aligned text)
  3. the animation to the detail screen takes place.

This looks bad, how can i fix it?


Solution

An optimal solution would look like this:

  • Main screen keeps bottom bar & fades out.
  • Simultaneously the detail page enters without a bottom bar.
3 Answers

So we went with a work-around:

  • on top-level the scaffold now does not contain a bottomBar anymore
  • each screen which needs it now has its own bottomBar.

This works fine, jus the ripple-click of the bottomBar isn't as smooth as we'd like it to be (we're exchanging it mid-click, so this is to be expected)

This also fixes an issue, where a screen had a scrollable content, which's scroll-distance got confused a little due to it changing when hiding the bottom bar.

Faced a similar issue in my code and this is how I solved it; I made use of the crossfade, you can read more https://developer.android.com/jetpack/compose/animation#crossfade and more about bottom nav from https://developer.android.com/jetpack/compose/navigation#bottom-nav

enum class HomeNavType {
    DASHBOARD, ACCOUNT
}

@Composable
fun HomeScreen(navController: NavController) {
    val homeNavItemState = rememberSaveable { mutableStateOf(HomeNavType.DASHBOARD) }
    Scaffold(
        content = { HomeContent(homeNavType = homeNavItemState.value , navController)},
        bottomBar = { HomeBottomNavigation(homeNavItemState) }
    )
}

@Composable
fun HomeBottomNavigation(homeNavItemState: MutableState<HomeNavType>) {
    BottomNavigation() {
        BottomNavigationItem(
            selected = homeNavItemState.value === HomeNavType.DASHBOARD,
            onClick = {
                homeNavItemState.value = HomeNavType.DASHBOARD
            },
            icon = {
                Icon(
                    painter = painterResource(id = R.drawable.ic_dashboard),
                    contentDescription = "Dashboard"
                )
            },
            label = { Text("Dashboard") },
        )
        BottomNavigationItem(
            selected = homeNavItemState.value === HomeNavType.ACCOUNT,
            onClick = {
                homeNavItemState.value = HomeNavType.ACCOUNT
            },
            icon = {
                Icon(
                    painter = painterResource(id = R.drawable.ic_person),
                    contentDescription = "Account"
                )
            },
            label = { Text("Account") },
        )
    }
}

@Composable
fun HomeContent(homeNavType: HomeNavType, navController: NavController) {
    Crossfade(targetState = homeNavType) { navType ->
        when (navType) {
            HomeNavType.DASHBOARD -> DashboardScreen(navController)
            HomeNavType.ACCOUNT -> AccountScreen(navController)
        }
    }
}

ensure to initialize navController and navHostEngine.Create a sealed class that holds the objects of the UIs to be added to the bottom navigation.within the scaffold add bottom bar iterate through the bottom navigation item and check if each item has the destination as route if true add bottom navigation with specified data needed.

val navController = rememberNavController()
val navHostEngine = rememberNavHostEngine()

val bottomNavigationItems: List<BottomNavItem> = listOf(
    BottomNavItem.MainScreen,
    BottomNavItem.FavoriteScreen,
    BottomNavItem.UserScreen
)

Scaffold(
bottomBar = {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination
    val route = navBackStackEntry?.destination?.route

    bottomNavigationItems.forEach { item ->
        if (item.destination.route == route) {
            BottomNavigation(
                backgroundColor = Color.White,
                contentColor = Color.Black
            ) {
                bottomNavigationItems.forEach { item ->
                    BottomNavigationItem(
                        icon = {
                            Icon(
                                imageVector = item.icon,
                                contentDescription = null
                            )
                        },
                        label = {
                            Text(text = item.title)
                        },
                        alwaysShowLabel = false,
                        selectedContentColor = green,
                        unselectedContentColor = Color.LightGray,
                        selected = currentDestination?.route?.contains(item.destination.route) == true,
                        onClick = {
                            navController.navigate(item.destination.route) {
                                navController.graph.startDestinationRoute?.let { screenRoute ->
                                    popUpTo(screenRoute) {
                                        saveState = true
                                    }
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )

                }
            }
        }
    }
}
) {
    paddingValues ->
    Box(
        modifier = Modifier.padding(paddingValues)
    ) {
        DestinationsNavHost(
            navGraph = NavGraphs.root,
            navController = navController,
            engine = navHostEngine
        )
    }

}
Related