Passing string typed data with Jetpack Compose navigation component
In this second part of the Jetpack Compose navigation series, we would learn how to pass string typed data using the compose navigation components while using its SafeArgs feature.
Here is the list of the blogs in this series:
- Part 1 — Implement Bottom Bar Navigation in Jetpack Compose
- Part 2 — Passing string typed data with Jetpack Compose navigation component.
- Part 3 — Passing multi typed of data with Jetpack Compose navigation component.

In the first part of the series, we wrote a small fun application using Jetpack Compose, where we implemented navigation with a bottom navigation bar.

The code for this app can be cloned from here.
In this, the second part of the series, we add to our capabilities by harnessing the power of the navigation SafeArgs feature.
to follow along with this article, you can checkout the safeArgs branch from that same repository.
What is SafeArgs?
Navigation allows you to attach data to a navigation operation by defining arguments for a destination.
When we navigate between screens in android we have multiple ways to share data between them.
- Intents — Intents are a basic component of the android framework allowing us to define a destinations as well as arguments to pass between them.
- Common classes— A repository, service, or view model, that holds said data and exposes it to the presentation layer.
The second option is still valid for Jetpack Compose if your app's architecture relies on view models or other classes as state holders.
However, as far as the first option goes, when we want to create a pure Compose app with no multiple activities or fragments, we can declare arguments as part of the destinations and extract them as needed.
Let’s remember how we declared the destinations in the previous example of our SpookyNavigation app.
The composable in this context is an extension function of NavGraphBuilder and allows us to add composables to the navigation graph programmatically, contrary to the way we used to work in the view system where we declared the navigation graph in XML.
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = listOf(),
deepLinks: List<NavDeepLink> = listOf(),
content: @Composable (NavBackStackEntry) -> Unit
) {...}
And as we can see, the composable extension function can take an argument list as a parameter and we would use that to pass arguments to the composable later on in the article.
In our last example where we implemented the bottom navigation bar, we navigated to a ScaryScreen() and we passed in the type of animation we wished to display:
ScaryScreen(ScaryAnimation.Frankendroid)
In this case, the animation type we passed to the ScaryScreen() was ScaryAnimation.Frankendroid.
Now let’s take a use case where the producer of the navigation action, (i.e the component that initiated the navigation action) is the one responsible for deciding on the animation type of the next screen.
Note: In this article, we would focus on passing string typed data since, according to the documentation:
By default, all arguments are parsed as strings.
In the next part of the series, we would tackle passing other types of data.
Let’s add some code.
Initiating a navigation action is as simple as:
navController.navigate(route)
Where “route” is a string representing the next destination.
Similar to an API path or a deep link path, we can append the data we wish to send to the route string.
So in our previous example, we added a sealed class called BottomNavigationScreens to encapsulate the different screens we can navigate to, along with some parameters for their bottom navigation item button, such as icons and texts.
When navigating screens we used this class to provide our routes.
So if we want to navigate to the “Pumpkin” screen, we call:
navController.navigate(BottomNavigationScreens.Pumpkin.route)
BottomNavigationScreens.Pumpkin.route is a variable representing the string “pumpkin”, now, we want to add an argument, the animation type, to that route.
How would we do that?.
We can append an argument to the route like so:
“Pumpkin/{<ARGUMENT>}”
So our route would be represented this way:
BottomNavigationScreens.Pumpkin.route + "/{<ARGUMENT>}”
Or with the kotlin plus operator:
BottomNavigationScreens.Pumpkin.route.plus("/{<ARGUMENT>}”)
Our argument is an integer representing the resource id of the animation we want to display, we can add this data as part of the screen data class we declared earlier, lets see how a BottomNavigationScreens object looks like now that we have added an animation type to it:
object Pumpkin : BottomNavigationScreens(
route: "Pumpkin",
resourceId: R.string.pumpkin_screen_route,
icon: Icons.Filled.FoodBank,
animId: ScaryAnimation.Pumpkin)
By using our BottomNavigationScreens class we can append the argument to our route like this:
val argument = 123456
route = screen.route.plus("/{$argument}")
navController.navigate(route)
We can encapsulate this code in a method and call it navigateWithArguments, which takes an optional argument and appends it to the route if it is provided.
And we can use this function in our BottomNavigationItem’s onClick lambda:
BottomNavigationScreens.Pumpkin -> {
navigateWithArguments(
"/${screen.scaryAnimation.animId}",
screen,
navController)
Extracting the argument
We are doing great! we added an argument to the route, we may have added an integer but let’s remember again that:
By default, all arguments are parsed as strings.
So no matter what type of argument we append, we append it as a string on the route, now it is time to extract it.
We declared our composable destination like this before:
composable(BottomNavigationScreens.Frankendroid.route) {
ScaryScreen(ScaryAnimation.Frankendroid.animId)
}
And now that we want to declare a composable destination that holds an argument, we would declare it like this:
composable(BottomNavigationScreens.Pumpkin.route
.plus("/{<ARGUMENT_KEY>}")) {
ScaryScreen(<THE ARGUMENT>)
}
Can you see our problem?
We have a route, to which we appended a string representing a key, Not the actual argument we want to pass.
And we need to somehow get the actual argument to provide the ScaryScreen with the animation type information.
We need to somehow extract the argument and pass it to the ScaryScreen.
Luckily, the composable lambda provides the NavBackStackEntry of the current declared composable and we can extract the argument from there using the key we declared on the composable route.
val scaryAnimationIdAsString = backStackEntry.arguments?.getString(<ARGUMENT_KEY>)
We can then pass that string to the ScaryScreen.
ScaryScreen(scaryAnimationIdAsString)
So our declared destination in the NavHost looks like this.
This is great! we now have a working example of how we can pass arguments of string types!
In the next article of the series, we would figure out how to pass other types of data and even optional types, using this SafeArgs feature.
As always, the source code for the SpookyNavigation app is at my GitHub repo, you can clone or fork it, and checkout the safeArgs branch for the code we implemented in this article.
Clap and share! but only if you think I earned it…. :)