Passing Arguments
What are Navigation Arguments?
Navigation arguments are like passing notes between screens. When you need to share information from one screen to another, you use arguments to carry that data along with the navigation. For example, when a user clicks on a product in a list, you need to pass the product ID to the detail screen to show the correct information.
Types of Arguments
| Type | What It's For | Example Use |
|---|---|---|
| String | Text data | User names, messages |
| Int | Whole numbers | IDs, counts |
| Float | Decimal numbers | Prices, measurements |
| Boolean | True/False values | Settings, flags |
Basic Argument Passing
@Composable
fun NavigationWithArgs() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
// Home screen route - starting point of the navigation
composable("home") {
HomeScreen(
onNavigateToProfile = { data ->
// Navigate to profile screen with a userId parameter
// The route will be constructed as "enteredText/$data"
navController.navigate("enteredText/$data")
}
)
}
// Profile screen route with argument
// The {data} in the route is a placeholder for the actual user ID
composable(
route = "enteredText/{data}",
// Define the argument type as String
arguments = listOf(
navArgument("data") { type = NavType.StringType }
)
) { backStackEntry ->
// Extract the data argument from the navigation back stack entry
val data = backStackEntry.arguments?.getString("data")
ProfileScreen(
data = data,
onNavigateBack = {
navController.popBackStack()
}
)
}
}
}
/**
* HomeScreen displays the main screen of the application.
* It contains a button that navigates to a specific user's profile.
*
* @param onNavigateToProfile Callback function that takes a userId parameter
* and handles navigation to the profile screen
*/
@Composable
fun HomeScreen(onNavigateToProfile: (String) -> Unit) {
var text by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
OutlinedTextField(
value = text,
onValueChange = {text = it},
label = { Text("Enter text")}
)
// Button that triggers navigation with a hardcoded user ID
// In a real app, this would typically come from a user selection or authentication
Button(onClick = { onNavigateToProfile(text) }) {
Text("Send text to profile screen")
}
}
}
/**
* ProfileScreen displays the profile information for a specific user.
* It shows the content the user passed through navigation and provides a way to go back.
*
* @param data The data the user entered.
* @param onNavigateBack Callback function to handle navigation back to the home screen
*/
@Composable
fun ProfileScreen(
data: String?,
onNavigateBack: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
// Display the user ID passed through navigation
Text("Entered Text: $data")
Button(onClick = onNavigateBack) {
Text("Go Back")
}
}
}
Understanding the Example Step by Step:
How Route Matching Works
The navigation system uses a pattern-matching system to determine which composable to show:
- When you call
navController.navigate("enteredText/$data"), the system looks for a matching route pattern - The pattern
"enteredText/{data}"in the composable definition acts like a template:enteredText/must match exactly{data}is a placeholder that matches any string
- When a match is found, the system:
- Extracts as the value for data
- Executes the lambda in the matching composable definition
- Shows the ProfileScreen with the extracted data
Understanding backStackEntry
The name backStackEntry can be confusing, but it's not about going back - it's about the current navigation state:
backStackEntryrepresents the current destination in the navigation stack- It's called "backStack" because Android maintains a stack of screens you've visited:
- When you navigate to a new screen, it's added to the top of the stack
- When you go back, the top screen is removed from the stack
- The
backStackEntrygives you access to the current screen's information
- In our example:
composable( route = "enteredText/{data}", // Define the argument type as String arguments = listOf( navArgument("data") { type = NavType.StringType } ) ) { backStackEntry -> // Extract the data argument from the navigation back stack entry val data = backStackEntry.arguments?.getString("data") ProfileScreen( data = data, onNavigateBack = { navController.popBackStack() } ) } - Think of
backStackEntryas "the current navigation state" rather than "going back" - It's just a parameter name - we could rename it to
currentDestinationornavEntryif we wanted
Mapping Routes to Composables
Important: The connection between the route name and the composable function is explicitly defined in the NavHost:
composable(
route = "enteredText/{data}",//this is just a string we made up
// Define the argument type as String
arguments = listOf(
navArgument("data") { type = NavType.StringType }
)
) { backStackEntry ->
// Extract the data argument from the navigation back stack entry
val data = backStackEntry.arguments?.getString("data")
ProfileScreen(
data = data,
onNavigateBack = {
navController.popBackStack()
}
)
}
- The route name "enteredText" is just a string we chose - it could be "userText" or "userData" or anything else
- We explicitly tell the navigation system to show ProfileScreen in the lambda after the route pattern
- There's no automatic connection between the route name and the composable name - we create this connection in our code
- This is why we could rename the route to "user/{userText}" and it would still show ProfileScreen, as long as we update the navigation call
navController.navigate("user/$userText")to match.
1. Setting Up Navigation
The NavigationWithArgs composable sets up our navigation structure:
rememberNavController()creates a controller to manage navigationNavHostdefines our navigation graph with "home" as the starting point- Two destinations are defined: "home" and "ProfileScreen"
2. Defining the Route with Arguments
In the ProfileScreen route, notice how we define the argument:
"enteredText/{data}"- The curly braces {data} indicate a dynamic argumentnavArgument("data")- Defines the argument and its type (StringType)- This tells the navigation system to expect a string value for data
3. Passing the Argument
In the HomeScreen, when the button is clicked:
onNavigateToProfile(text)is called- This triggers
navController.navigate("enteredText/$data") - The $data is replaced with the actual text entered, making the full route "enteredText/yourTextHere"
4. Receiving the Argument
In the ProfileScreen destination:
backStackEntry.arguments?.getString("data")retrieves the passed argument- The ?. operator safely handles the case where arguments might be null
- The retrieved data is then passed to ProfileScreen as a parameter
5. Using the Argument
In ProfileScreen:
- The data parameter is used to display the passed text
- Note that data is nullable (String?) for safety
- The back button uses popBackStack() to return to the previous screen
Key Points:
- Arguments are defined in the route string with {argumentName}
- Each argument needs a type definition
- Arguments are accessed from backStackEntry
- Always handle nullable arguments safely
How this example renders
Above is just a snippet of the code to view the full code, you need to go to my GitHub page and look at the chapter9 passingArgs.kt file.
Multiple Arguments
@Composable
fun NavigationWithMultipleArgs() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProduct = { id, name ->
navController.navigate("product/$id/$name")
}
)
}
composable(
"product/{id}/{name}",
arguments = listOf(
navArgument("id") { type = NavType.IntType },
navArgument("name") { type = NavType.StringType }
)
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("id")
val name = backStackEntry.arguments?.getString("name")
ProductScreen(
id = id,
name = name,
onNavigateBack = {
navController.popBackStack()
}
)
}
}
}
@Composable
fun HomeScreen(onNavigateToProduct: (Int, String) -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Available Products",
modifier = Modifier.padding(bottom = 16.dp)
)
// Sample product list - in a real app, this would come from a data source
val products = listOf(
Product(1, "Smartphone"),
Product(2, "Laptop"),
Product(3, "Headphones"),
Product(4, "Tablet")
)
// Display each product as a button
products.forEach { product ->
Button(
onClick = { onNavigateToProduct(product.id, product.name) },
modifier = Modifier.fillMaxWidth()
) {
Text("View ${product.name}")
}
}
}
}
@Composable
fun ProductScreen(
id: Int?,
name: String?,
onNavigateBack: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Display product details
Text(
text = "Product Details",
modifier = Modifier.padding(bottom = 8.dp)
)
// Show product information if available
if (id != null && name != null) {
Text("Product ID: $id")
Text("Product Name: $name")
// Additional product details could be added here
// For example: description, price, specifications, etc.
} else {
Text("Error: Product information not available")
}
// Back button
Button(
onClick = onNavigateBack,
modifier = Modifier.padding(top = 16.dp)
) {
Text("Back to Products")
}
}
}
data class Product(
val id: Int,
val name: String
)
@Composable
fun ProfileScreen(
userId: String?,
onNavigateBack: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 50.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
// Display the user ID passed through navigation
Text("Profile for user: $userId")
Button(onClick = onNavigateBack) {
Text("Go Back")
}
}
}
Understanding the Multiple Arguments Example
1. Data Structure
First, we define a simple data class to represent our products:
data class Product(val id: Int, val name: String)creates a template for our product data- This makes it easy to create and manage product information
- In a real app, this data would typically come from a database or API
2. Home Screen with Product List
The HomeScreen composable shows a list of products:
- Creates a sample list of products using our Product data class
- Uses
ColumnwithArrangement.spacedBy(8.dp)to space items evenly - Each product is displayed as a button using
forEach - When a product button is clicked, it calls
onNavigateToProduct(product.id, product.name) - This passes both the ID and name to the navigation system
3. Product Screen with Multiple Arguments
The ProductScreen composable receives and displays both arguments:
- Takes both
id: Int?andname: String?as parameters - Uses null safety checks (
if (id != null && name != null)) to handle missing data - Displays the product information in a clean, organized layout
- Includes a back button that uses
onNavigateBackto return to the product list
4. Navigation Setup
The navigation is set up to handle multiple arguments:
- Route pattern is
"product/{id}/{name}"- defines two parameters in the URL - Both arguments are defined in the
navArgumentlist:idis defined asNavType.IntTypenameis defined asNavType.StringType
- When navigating, both values are passed in the URL:
"product/1/Smartphone"
5. Key Differences from Single Argument Example
- Multiple arguments are passed in the URL path, separated by slashes
- Each argument needs its own type definition in the
navArgumentlist - The receiving composable needs to handle multiple nullable parameters
- Navigation calls need to provide all required arguments in the correct order
How this example renders
Above is just a snippet of the code to view the full code, you need to go to my GitHub page and look at the chapter9 multiple.kt file.
Optional Arguments
composable(
"profile/{userId}?showDetails={showDetails}",
arguments = listOf(
navArgument("userId") { type = NavType.StringType },
navArgument("showDetails") {
type = NavType.BoolType
defaultValue = false
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
val showDetails = backStackEntry.arguments?.getBoolean("showDetails") ?: false
ProfileScreen(
userId = userId,
showDetails = showDetails,
onNavigateBack = {
navController.popBackStack()
}
)
}
Understanding Optional Arguments
1. How Optional Arguments Work
In the example, we define an optional argument using a special syntax in the route pattern:
"profile/{userId}?showDetails={showDetails}"- The?afteruserIdindicates thatshowDetailsis optional- This means you can navigate to this screen in two ways:
- With the optional argument:
"profile/user123?showDetails=true" - Without the optional argument:
"profile/user123"
- With the optional argument:
2. Defining Optional Arguments
In the navArgument list, we specify that showDetails is optional:
navArgument("showDetails") {
type = NavType.BoolType
defaultValue = false // This makes it optional
}
- The
defaultValueproperty is what makes the argument optional - If the argument isn't provided in the URL, it will use this default value
- In this case, if
showDetailsisn't specified, it defaults tofalse
3. Using Optional Arguments
When navigating to the screen, you have flexibility in how you pass the arguments:
// Navigate with the optional argument
navController.navigate("profile/user123?showDetails=true")
// Navigate without the optional argument
navController.navigate("profile/user123") // showDetails will be false
4. Receiving Optional Arguments
In the composable, you can safely access the optional argument:
val showDetails = backStackEntry.arguments?.getBoolean("showDetails") ?: false
- The
?: falseis a fallback in case the argument is null - This ensures
showDetailsalways has a value - You can then use this value to conditionally show content:
if (showDetails) { // Show additional profile details } else { // Show basic profile information }
5. Common Use Cases for Optional Arguments
- Feature flags or toggles (like showing detailed vs. basic views)
- Filtering or sorting options
- Display preferences (like dark mode or list/grid view)
- Any parameter that isn't always needed but provides additional functionality when present
6. Best Practices
- Always provide sensible default values for optional arguments
- Use optional arguments sparingly - too many can make navigation confusing
- Document which arguments are optional in your code comments
- Consider using type-safe arguments for complex optional parameters
Tips for Success
- Use meaningful argument names
- Always handle nullable arguments
- Consider using type-safe arguments
- Keep argument names consistent
Common Mistakes to Avoid
- Forgetting to define argument types
- Not handling nullable arguments
- Using wrong argument types
- Forgetting to pass required arguments
Navigation vs State Management
It's important to understand the difference between passing arguments through navigation and managing state:
- Navigation Arguments:
- Used to pass data when moving between screens
- Data is passed once during navigation
- Arguments don't automatically update if they change
- Good for initial screen setup or one-time data passing
- State Management (covered in Chapter 10):
- Used to maintain data that needs to stay in sync
- Data can be updated and shared between screens
- Changes are reflected across all screens automatically
- Good for data that needs to be shared and updated
Example: If you're passing a user ID to a profile screen, use navigation arguments. If you need to share and update user preferences across multiple screens, use state management (which you'll learn about in Chapter 10).