HI, I'm implementing a bottom navigation bar in Je...
# compose-android
v
HI, I'm implementing a bottom navigation bar in Jetpack Compose using
NavHostController
, but the navigation bar does not appear even though my condition to determine its visibility seems correct.
Here’s my setup: 1. I have
@Serializable
objects representing my destinations:
Copy code
@Serializable
data object ProfileScreen

@Serializable
data object FeatureOne

@Serializable
object HomeScreenNavigation

@Serializable
object RegisterNavigation

@Serializable
object SignInNavigation
2. My
HomeTopLevelNavigation
composable initializes a list of top-level destinations and determines if the navigation bar should be visible based on the current destination:
Copy code
import android.util.Log
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navOptions
import com.hub.travelandroid.home.ui.R
import com.hub.travelandroid.home.ui.model.HomeTopLevelDestination
import com.hub.travelandroid.home.ui.screen.FeatureOne
import com.hub.travelandroid.home.ui.screen.ProfileScreen

@Composable
fun HomeTopLevelNavigation(navController: NavHostController) {
    val homeTopLevelDestinations = listOf(
        HomeTopLevelDestination(
            titleResId = R.string.profile_menu_item,
            route = ProfileScreen.toString()
        ),
        HomeTopLevelDestination(
            titleResId = R.string.profile_menu_item,
            route = FeatureOne.toString(),
        ),
    )
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination

    var selectedDestinationRoute by rememberSaveable { mutableStateOf(homeTopLevelDestinations[0].route) }

    val isNavigationBarVisible =
        currentDestination?.hierarchy?.any { destination ->
            homeTopLevelDestinations.any { it.route == destination.route }
        } == true

    Log.e("TAG", "HomeTopLevelNavigation: $isNavigationBarVisible")
    Log.e("TAG", "HomeTopLevelNavigation: currentDestination ${currentDestination?.route}")
    Log.e(
        "TAG",
        "HomeTopLevelNavigation: homeTopLevelDestinations ${homeTopLevelDestinations.first().route}"
    )
    if (isNavigationBarVisible)
        HomeNavigationBar(
            homeTopLevelDestinations = homeTopLevelDestinations,
            currentDestinationRoute = currentDestination?.route,
            onNavigate = { destination ->
                navController.navigate(
                    destination.route,
                    navOptions {
                        popUpTo(navController.graph.findStartDestination().id) {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                )
                selectedDestinationRoute = destination.route
            }
        )
}

@Composable
fun HomeNavigationBar(
    homeTopLevelDestinations: List<HomeTopLevelDestination>,
    currentDestinationRoute: String?,
    onNavigate: (HomeTopLevelDestination) -> Unit
) {
    NavigationBar {
        homeTopLevelDestinations.forEach { destination ->
            NavigationBarItem(
                icon = {},
                label = { Text(stringResource(destination.titleResId)) },
                selected = currentDestinationRoute == destination.route,
                onClick = {
                    onNavigate(destination)
                },
            )
        }
    }
}
3. My navigation graph looks like this:
Copy code
@Composable
fun AppNavHost(
    startDestination: Any,
    navHostController: NavHostController = rememberNavController()
) {
    val currentActivity = LocalActivity.current

    Scaffold(
        bottomBar = {
            HomeTopLevelNavigation(navHostController)
        }
    ) {
        NavHost(
            navController = navHostController,
            startDestination = startDestination
        ) {
            registerScreen()
            signInScreen()
            homeScreen(
                onBackPress = {
                    currentActivity?.finish()
                }
            )
        }
    }
}

fun NavGraphBuilder.homeScreen(onBackPress: () -> Unit) {
    navigation<HomeScreenNavigation>(startDestination = ProfileScreen) {
        composable<ProfileScreen> {
            HomeScreenRoute(onBackPress = onBackPress)
        }

        composable<FeatureOne> {
            Text("Feature 1")
        }
    }
}

data class HomeTopLevelDestination(
    @StringRes val titleResId: Int,
    val route: String,
)
Questions: 1. Am I correctly determining whether to show the navigation bar? 2. Could
toString()
on
@Serializable
objects be causing unexpected behavior in route comparisons? 3. How can I ensure that the navigation bar is correctly displayed when on top-level destinations? Any help would be appreciated!
s
If you hard-code
isNavigationBarVisible = true
, does the navigation bar appear correctly?
v
yes it working fine without any conditions