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:
- I have
@Serializable
objects representing my destinations:
@Serializable
data object ProfileScreen
@Serializable
data object FeatureOne
@Serializable
object HomeScreenNavigation
@Serializable
object RegisterNavigation
@Serializable
object SignInNavigation
- My
HomeTopLevelNavigation
composable initializes a list of top-level destinations and determines if the navigation bar should be visible based on the current destination:
import android.util.Log
import androidxpose.material3.NavigationBar
import androidxpose.material3.NavigationBarItem
import androidxpose.material3.Text
import androidxpose.runtime.Composable
import androidxpose.runtime.getValue
import androidxpose.runtime.mutableStateOf
import androidxpose.runtime.saveable.rememberSaveable
import androidxpose.runtime.setValue
import androidxpose.ui.res.stringResource
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigationpose.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)
},
)
}
}
}
- My navigation graph looks like this:
@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:
- Am I correctly determining whether to show the navigation bar?
- Could
toString()
on@Serializable
objects be causing unexpected behavior in route comparisons? - How can I ensure that the navigation bar is correctly displayed when on top-level destinations?
UPDATE
I tried to use Json.encodeToString()
from here but still not working
val homeTopLevelDestinations = listOf(
HomeTopLevelDestination(
titleResId = R.string.profile_menu_item,
route = Json.encodeToString(ProfileScreen)
),
HomeTopLevelDestination(
titleResId = R.string.profile_menu_item,
route = Json.encodeToString(FeatureOne),
),
)
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:
- I have
@Serializable
objects representing my destinations:
@Serializable
data object ProfileScreen
@Serializable
data object FeatureOne
@Serializable
object HomeScreenNavigation
@Serializable
object RegisterNavigation
@Serializable
object SignInNavigation
- My
HomeTopLevelNavigation
composable initializes a list of top-level destinations and determines if the navigation bar should be visible based on the current destination:
import android.util.Log
import androidxpose.material3.NavigationBar
import androidxpose.material3.NavigationBarItem
import androidxpose.material3.Text
import androidxpose.runtime.Composable
import androidxpose.runtime.getValue
import androidxpose.runtime.mutableStateOf
import androidxpose.runtime.saveable.rememberSaveable
import androidxpose.runtime.setValue
import androidxpose.ui.res.stringResource
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigationpose.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)
},
)
}
}
}
- My navigation graph looks like this:
@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:
- Am I correctly determining whether to show the navigation bar?
- Could
toString()
on@Serializable
objects be causing unexpected behavior in route comparisons? - How can I ensure that the navigation bar is correctly displayed when on top-level destinations?
UPDATE
I tried to use Json.encodeToString()
from here but still not working
val homeTopLevelDestinations = listOf(
HomeTopLevelDestination(
titleResId = R.string.profile_menu_item,
route = Json.encodeToString(ProfileScreen)
),
HomeTopLevelDestination(
titleResId = R.string.profile_menu_item,
route = Json.encodeToString(FeatureOne),
),
)
Share
Improve this question
edited yesterday
halfer
20.4k19 gold badges109 silver badges202 bronze badges
asked Mar 17 at 18:51
Vivek ModiVivek Modi
7,46320 gold badges103 silver badges232 bronze badges
1 Answer
Reset to default 0Your visibility logic is fine, but using toString() on your @Serializable objects can return unexpected values. Instead, define explicit route strings for your destinations and use those for consistent navigation comparisons.
Also, instead of using toString() you can replace it with decoding/encoding:
https://kotlinlang./docs/serialization.html#serialize-and-deserialize-json