ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Type Safe Bottom Navigation in Jetpack Compose

Konstantin Merenkov
ProAndroidDev
Published in
4 min readJul 7, 2024

--

Intro

While many articles cover the basics of the new Compose Navigation, this guide dives deeper into the Type Safe BottomBar routes.

We’ll explore advanced concepts and practical implementations to enhance your understanding and application of Type Safe Navigation in Jetpack Compose.

Getting started

If you’re unfamiliar with the new Type Safe (or “Serializable”) navigation in Jetpack Compose, I highly recommend checking out the Official Documentation before proceeding.

Essentially, we can specify our navigation routes as Serializable objects, such as data objects and data classes.

It’s important to note that this approach won’t work with other types. For example, using a sealed class would cause the following error:

IllegalArgumentException: Cannot cast value of type kotlinx.serialization.Sealed<HOME> to a NavType. 
Make sure to provide custom NavType for this argument.

From Basic to Advanced Navigation

Let’s transform our basic Compose routes from the traditional setup:

sealed class Destinations(val route: String) {
data object HOME : Destinations("home")
data object PROFILE : Destinations("profile?userId={userId}")
}

fun Destinations.PROFILE.buildRoute(userId: String) = "profile?userId=$userId"

NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,
startDestination = Destinations.HOME.route
) {
composable(route = Destinations.HOME.route) {
HomeScreen(
toProfileScreen = {
navController.navigate(Destinations.PROFILE.buildRoute("101"))
}
)
}
composable(
route = Destinations.PROFILE.route,
arguments = listOf(navArgument("userId") { defaultValue = "" })
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId = userId)
}
}

To a type-safe setup:

sealed class Destinations {
@Serializable
data object Home: Destinations()

@Serializable
data class ProfileInfo(val userId: String): Destinations()
}

NavHost(
modifier = Modifier
.fillMaxSize()
.padding(it),
navController = navController,
startDestination = Destinations.Home
) {
composable<Destinations.Home> {
HomeScreen(
toProfileInfoScreen = {
navController.navigate(Destinations.ProfileInfo("101"))
}
)
}
composable<Destinations.ProfileInfo> { backStackEntry ->
val profileInfo = backStackEntry.toRoute<Destinations.ProfileInfo>()
ProfileInfoScreen(userId = profileInfo.userId)
}
}

This new approach eliminates the need for route build functions and navArguments in profile navigation, simplifying the code significantly.

BottomBar Navigation

Now, let’s create a sample BottomBar with three routes using the type safe navigation. Our tabs are: Home, Search, and Profile. Note that the Search tab route includes an argument.

Determining the Active Tab

To determine which tab to set active based on the current navigation route, we previously used navBackStackEntry to get the destination and compared it with our string route from the BottomItem:

val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route ?: BottomRoutes.HOME.route

Route is a nullable string, which is set to the default route in case of null.

With type-safe navigation, the destination route string for the data object is the qualifiedName of the route.

For example: com.merkost.composenavigation.ui.Destinations.Home

Note: qualifiedName returns the fully qualified dot-separated name of the class, or null if the class is local or a class of an anonymous object.

Generating Route Patterns

The generated route pattern contains the path (which is a qualified name), path args, and query args. For instance, a data class route looks like this:

@Serializable
data class Search(val searchText: String? = null)

//com.merkost.composenavigation.ui.Destinations.Search?searchText={searchText}

This route is constructed by the generateRouteWithArgs function. While the function usage is restricted by the Navigation library, you still can generate the route for your class:

val searchRoute = generateRouteWithArgs(
route = Destinations.Search(),
typeMap = mapOf(
"searchText" to NavType.StringType as NavType<Any?>
)
)

Bottom Navigation Routes with Arguments

If you use routes with arguments as your BottomBar navigation routes, first of all, you should instantiate such classes:

enum class BottomNavigation(val label: String, val icon: ImageVector, val route: Destinations) {
HOME("Home", Icons.Filled.Home, Destinations.Home),
SEARCH("Search", Icons.Filled.Search, Destinations.Search()), // Here!
PROFILE("Profile", Icons.Filled.AccountCircle, Destinations.Profile)
}

Failure to instantiate the class will result in the following exception:

kotlinx.serialization.SerializationException: Serializer for class ‘Companion’ is not found.

Current Route Handling for BottomBar Navigation

Here’s how to handle the current route for BottomBar navigation:

//To get the currentRoute:
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentHierarchy = navBackStackEntry?.destination?.hierarchy

//To compare with the bottomBar route
val isSelected by remember(currentRoute) {
derivedStateOf { currentHierarchy.any { it.hasRoute(navigationItem.route::class) } }
}

We are taking the whole current destination hierarchy to cover nested bottom tab’s navigation as well. Then, simply checking if any entry in the hierarchy has current tab route. If it has — that means tab should be selected.

Conclusion

By adopting Type Safe Navigation in Jetpack Compose, we can significantly reduce the complexity of our navigation setup while ensuring type safety and readability. This guide has covered the type safe BottomBar routes, providing an understanding of the new navigation system.

For further exploration, refer to the Official Documentation and go ahead integrating the type safe navigation into your projects!

Full code of the discussed approched (both old-fashioned and type safe)

Other useful links:

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Konstantin Merenkov

Android Engineer and Jetpack Compose lover ❤️

Responses (4)