Mastering Navigation Arguments Passing in Jetpack Compose

In modern Android development using Jetpack Compose, navigation plays a pivotal role in creating seamless user experiences. Passing arguments between composables during navigation is a common requirement. This article explores the various methods to effectively pass navigation arguments in Jetpack Compose.

What are Navigation Arguments?

Navigation arguments are data passed from one screen (composable) to another during navigation. They enable the destination composable to render its UI based on the received data.

Why Pass Arguments During Navigation?

  • Dynamic UI Rendering: Render UI elements based on dynamic data passed during navigation.
  • Screen Personalization: Tailor content to the user’s preferences or context.
  • Data Transfer: Transfer data needed for subsequent operations between screens.

Methods to Pass Navigation Arguments in Jetpack Compose

There are several ways to pass navigation arguments in Jetpack Compose. Let’s explore the common and effective methods.

Method 1: Using NavController.navigate with URI Syntax

One of the simplest ways to pass arguments is using the URI syntax in the NavController.navigate method.

Step 1: Define the Route with Arguments

In your navigation graph, define the route for the destination composable and include argument placeholders:


import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument

@Composable
fun NavigationGraph() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(navController = navController)
        }
        composable(
            route = "details/{itemId}/{itemName}",
            arguments = listOf(
                navArgument("itemId") { type = NavType.IntType },
                navArgument("itemName") { type = NavType.StringType }
            )
        ) { entry ->
            val itemId = entry.arguments?.getInt("itemId")
            val itemName = entry.arguments?.getString("itemName")
            DetailsScreen(itemId = itemId, itemName = itemName)
        }
    }
}

Here:

  • We define a route "details/{itemId}/{itemName}" with two arguments: itemId (IntType) and itemName (StringType).
  • navArgument is used to specify each argument and its type.
Step 2: Navigate with Arguments

Use navController.navigate to navigate to the destination composable and pass the arguments:


import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController

@Composable
fun HomeScreen(navController: NavController) {
    Button(onClick = {
        navController.navigate("details/123/ExampleItem")
    }) {
        Text("Navigate to Details")
    }
}
Step 3: Retrieve Arguments in the Destination Composable

Retrieve the passed arguments from the NavBackStackEntry in the destination composable:


import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

@Composable
fun DetailsScreen(itemId: Int?, itemName: String?) {
    Text("Item ID: $itemId, Item Name: $itemName")
}

Method 2: Using Named Navigation Arguments

You can also pass arguments by building a navigation route using named arguments, which improves readability and maintainability.

Step 1: Define Route with Named Arguments

Specify named arguments in the route definition:


composable(
    route = "profile?userId={userId}&userName={userName}",
    arguments = listOf(
        navArgument("userId") {
            type = NavType.IntType
            defaultValue = 0 // Default value if the argument is not passed
        },
        navArgument("userName") {
            type = NavType.StringType
            nullable = true // Allow the argument to be nullable
        }
    )
) { entry ->
    val userId = entry.arguments?.getInt("userId") ?: 0
    val userName = entry.arguments?.getString("userName") ?: "Guest"
    ProfileScreen(userId = userId, userName = userName)
}

In this example:

  • "profile?userId={userId}&userName={userName}" defines the route with named arguments userId and userName.
  • defaultValue provides a default value if the argument is not provided during navigation.
  • nullable = true allows the argument to be nullable.
Step 2: Navigate with Named Arguments

Construct the navigation route with the argument values:


Button(onClick = {
    navController.navigate("profile?userId=456&userName=JohnDoe")
}) {
    Text("Navigate to Profile")
}
Step 3: Retrieve Named Arguments

Retrieve the passed arguments in the destination composable:


import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

@Composable
fun ProfileScreen(userId: Int, userName: String) {
    Text("User ID: $userId, User Name: $userName")
}

Method 3: Using Parcelable/Serializable Objects

For complex data, you can use Parcelable or Serializable objects to pass data between composables.

Step 1: Create a Parcelable Data Class

Create a data class that implements the Parcelable interface:


import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class User(val id: Int, val name: String, val email: String) : Parcelable

Make sure to include the kotlin-parcelize plugin in your build.gradle file:


plugins {
    id 'kotlin-parcelize'
}
Step 2: Convert Parcelable Object to JSON

Use the Gson library to convert the Parcelable object to a JSON string and pass it as a navigation argument:


import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.google.gson.Gson

@Composable
fun HomeScreen(navController: NavController) {
    val user = User(id = 789, name = "JaneDoe", email = "jane.doe@example.com")
    val userJson = Gson().toJson(user)

    Button(onClick = {
        navController.navigate("userProfile/$userJson")
    }) {
        Text("Navigate to User Profile")
    }
}
Step 3: Receive and Parse the JSON Argument

Receive the JSON string and convert it back to the Parcelable object in the destination composable:


import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.google.gson.Gson

fun NavGraphBuilder.userProfileRoute() {
    composable(
        route = "userProfile/{userJson}",
        arguments = listOf(
            navArgument("userJson") {
                type = NavType.StringType
            }
        )
    ) { entry ->
        val userJson = entry.arguments?.getString("userJson")
        val user = Gson().fromJson(userJson, User::class.java)
        UserProfileScreen(user = user)
    }
}

@Composable
fun UserProfileScreen(user: User) {
    Text("User ID: ${user.id}, Name: ${user.name}, Email: ${user.email}")
}

Conclusion

Effectively passing navigation arguments in Jetpack Compose is crucial for creating dynamic and personalized UIs. You can choose the method that best suits your data complexity and app requirements: using URI syntax, named arguments, or passing Parcelable objects via JSON. Each approach has its own strengths and use cases, allowing you to implement robust navigation with seamless data transfer in your Jetpack Compose applications.