androidkotlinandroid-architecture-navigationandroid-safe-args

Using safeargs to create deep links


I am creating an explicit deeplink to a fragment in my app (Navigation component). This is how I am currently doing it:

val args = bundleOf(
    "roomId" to roomId,
    "receiver" to receiver
)
return NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph_home)
    .addDestination(R.id.navigation_chat_private)
    .setArguments(args)
    .createPendingIntent()

The arguments are defined in the nav_graph like this:

<argument android:name="roomId" app:argType="string" />
<argument android:name="receiver" app:argType="integer" />

This works without issue. But I can't help but wonder if there is a way to avoid hardcoding the parameter names twice. I feel like there HAS to be a better way. Something along the lines of:

val chatArgs = NavigationChatPrivateArgs(roomId,receiver)
return NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph_home)
    .addDestination(R.id.navigation_chat_private)
    .setArguments(chatArgs.toBundle())
    .createPendingIntent()

But there is no such constructor in the generated safeArgs class. I also tried calling the empty constructor and then setting the values, but the args are vals, so cannot be reassigned. Is anybody aware of an elegant way to do this?


Solution

  • When you use the Safe Args plugin to generate Kotlin code via the androidx.navigation.safeargs.kotlin plugin, each Args class is given a constructor that uses arguments for required arguments and default arguments for parameters that have a defaultValue or that are nullable.

    This lets you generate exactly the code you've posted:

    val chatArgs = NavigationChatPrivateArgs(roomId,receiver)
    val bundle = chatArgs.toBundle()
    

    However, when you generate Java code via the androidx.navigation.safeargs plugin, default arguments are not available. Therefore the Java code uses a builder pattern - you construct a NavigationChatPrivageArgs.Builder instance, which takes required arguments as constructor parameters for the Builder class and optional parameters as additional setters available on the Builder and then call build() on the Builder to construct the class.

    // Assuming both parameters are required
    val chatArgs = NavigationChatPrivateArgs.Builder(roomId,receiver).build()
    val bundle = chatArgs.toBundle()
    

    So in all cases, yes, you can directly create your Args class exactly for use in cases like this or in tests.