Question

Android Navigation Safe Args Directions Classes in Kotlin

kotlinandroid-navigationandroid-jetpack

Question

I am using the Android Navigation component and Safe Args to pass data. My navigation graph defines an argument for a fragment:

<fragment
    android:id="@+id/otherFragment"
    android:name="com.asd.navigate.OtherFragment"
    android:label="OtherFragment">

    <argument
        android:name="screenTitle"
        android:defaultValue="0"
        app:argType="string" />
</fragment>

OtherFragmentArgs is generated and usable, but no OtherFragmentDirections class is generated after building the project. Is this a Safe Args bug, or must I define something else?

My Gradle setup includes the Safe Args plugin:

buildscript {
    dependencies {
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha01"
    }
}
apply plugin: "androidx.navigation.safeargs"

Short Answer

Safe Args generates different classes for two different jobs: Args classes read arguments received by a destination, while Directions classes create navigation actions that send arguments. You will learn why an argument alone creates an Args class, how to define an action that produces a Directions class, and how to navigate safely from Kotlin.

Concept

Safe Args is code generation for the Android Navigation component. It reads your navigation graph XML during the build and produces Kotlin or Java APIs that make navigation arguments type-safe.

Two generated APIs are especially important:

  • OtherFragmentArgs represents data that arrives at OtherFragment.
  • SomeFragmentDirections represents navigation actions that leave from SomeFragment.

An <argument> inside a <fragment> declares that the fragment can receive a value. Therefore, Safe Args can generate OtherFragmentArgs immediately.

A Directions class is different: it is based on one or more <action> elements. Your graph currently declares a destination and an argument, but it does not declare an action from any source destination to otherFragment. With no outgoing action, Safe Args has no direction method to generate.

Also note the naming: Safe Args normally generates OtherFragmentDirections (plural), not OtherFragmentDirection.

In the original XML, use app:argType="string" for the argument type. The android:defaultValue attribute is valid for the default value, but app:type is not the Safe Args argument-type attribute.

Mental Model

Think of fragments as rooms in a building.

  • An argument is a form placed inside a room. When somebody enters, the room can read the form. That is OtherFragmentArgs.
  • An action is a labeled route leaving a room, such as “Go to Other Fragment.” That route can carry a completed form. That is a method in SomeFragmentDirections.

Putting a form in OtherFragment does not automatically tell the navigation system which room people will travel from. You must add a route—an <action>—to create a directions method.

Take Quiz

Syntax and Examples

Declare the receiving destination and its argument:

<fragment
    android:id="@+id/otherFragment"
    android:name="com.asd.navigate.OtherFragment"
    android:label="OtherFragment">

    <argument
        android:name="screenTitle"
        android:defaultValue="Untitled"
        app:argType="string" />
</fragment>

This produces an OtherFragmentArgs class. In OtherFragment, read the value with the Navigation Fragment Kotlin extension:

class OtherFragment : Fragment(R.layout.fragment_other) {

    private val args: OtherFragmentArgs by navArgs()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val title = args.screenTitle
        requireActivity().title = title
    }
}

To generate a directions class, define a source fragment and add an action to the destination:

<fragment
    android:id="@+id/homeFragment"
    android:name="com.asd.navigate.HomeFragment"
    android:label="Home">

    <action
        android:id="@+id/action_homeFragment_to_otherFragment"
        app:destination="@id/otherFragment" />
</fragment>

<fragment
    android:id="@+id/otherFragment"
    android:name="com.asd.navigate.OtherFragment"
    android:label="OtherFragment">

    <argument
        android:name="screenTitle"
        android:defaultValue="Untitled"
        app:argType="string" />
</fragment>

After rebuilding, Safe Args generates HomeFragmentDirections. Use it from HomeFragment:

findNavController().navigate(
    HomeFragmentDirections.actionHomeFragmentToOtherFragment(
        screenTitle = "Account settings"
    )
)

The directions class belongs to the source fragment because that fragment owns the outgoing action.

Step by Step Execution

Consider this graph:

<fragment
    android:id="@+id/homeFragment"
    android:name="com.asd.navigate.HomeFragment">

    <action
        android:id="@+id/action_homeFragment_to_otherFragment"
        app:destination="@id/otherFragment" />
</fragment>

<fragment
    android:id="@+id/otherFragment"
    android:name="com.asd.navigate.OtherFragment">

    <argument
        android:name="screenTitle"
        app:argType="string" />
</fragment>

And this Kotlin code:

val direction = HomeFragmentDirections
    .actionHomeFragmentToOtherFragment("Welcome")

findNavController().navigate(direction)

Execution flow:

  1. The build reads the navigation graph.
  2. It sees screenTitle on otherFragment, so it generates OtherFragmentArgs.
  3. It sees an action leaving homeFragment, so it generates HomeFragmentDirections.
  4. actionHomeFragmentToOtherFragment("Welcome") creates a direction object containing the destination ID and screenTitle value.
  5. navigate(direction) asks the NavController to follow that action.
  6. Android displays OtherFragment and stores the value as navigation arguments.
  7. OtherFragmentArgs.fromBundle(...), or by navArgs(), reads screenTitle as a String.

If the action does not exist, step 3 cannot happen. That is why no directions class is generated.

Real World Use Cases

Safe Args directions are useful whenever one screen starts navigation and must provide typed input to the next screen.

  • Product details: ProductListFragmentDirections.actionProductListToDetails(productId)
  • Profile editing: pass a user ID from a profile screen to an edit screen.
  • Search results: pass a query string and optional filter values.
  • Checkout: pass a cart ID rather than a large mutable cart object.
  • Notifications and deep links: use destination arguments to identify the content a user should see.

Arguments keep navigation contracts visible in the navigation graph and reduce errors caused by manually typed argument keys.

Take Quiz

Real Codebase Usage

In real projects, developers generally define actions for normal screen-to-screen flows and use generated directions at the navigation call site.

A common click-handler pattern is:

binding.settingsButton.setOnClickListener {
    val direction = HomeFragmentDirections
        .actionHomeFragmentToOtherFragment("Settings")

    findNavController().navigate(direction)
}

At the destination, read arguments once and validate important values early:

private val args: OtherFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val title = args.screenTitle
    if (title.isBlank()) return

    binding.title.text = title
}

Useful project conventions include:

  • Pass stable identifiers such as productId or userId, then load the full data through a repository or ViewModel.
  • Keep argument names descriptive and consistent, such as orderId rather than id.
  • Define actions at the source destination when the flow is specific to that screen.
  • Use destination-level arguments when several actions or deep links can open the same destination.
  • Avoid navigating twice from rapid taps; apps often disable the triggering view briefly or check the current destination.

Common Mistakes

Expecting a directions class from an argument alone

This destination can generate OtherFragmentArgs, but it has no outgoing action:

<fragment
    android:id="@+id/otherFragment"
    android:name="com.asd.navigate.OtherFragment">
    <argument
        android:name="screenTitle"
        app:argType="string" />
</fragment>

Fix: Add an <action> under the fragment that starts the navigation. The resulting directions class belongs to that source fragment.

Looking for the wrong name

This is incorrect:

OtherFragmentDirection

Safe Args uses the plural class name:

OtherFragmentDirections

However, it is generated only if OtherFragment itself has outgoing actions.

Expecting the destination to own the action

For this action:

<action
    android:id="@+id/action_homeFragment_to_otherFragment"
    app:destination="@id/otherFragment" />

the generated class is HomeFragmentDirections, not OtherFragmentDirections.

Using the wrong argument attribute

This is not the Safe Args type attribute:

<argument
    android:name="screenTitle"
    app:type="string" />

Use app:argType:

<argument
    android:name="screenTitle"
    app:argType="string" />

Mixing old and new Navigation dependencies

The question uses an old alpha-era plugin coordinate. In a current project, keep all Navigation libraries and the Safe Args plugin on compatible versions, and use the plugin ID recommended by the current Android Navigation documentation. Dependency or plugin-version mismatches can prevent generated code from appearing.

Importing the wrong generated package

Generated classes are placed in a package based on the navigation graph and application namespace. Let Android Studio add the import, or inspect the generated sources if the class exists but is not found by an import.

Comparisons

ItemWhat generates itWhere it is usedTypical example
OtherFragmentArgs<argument> on otherFragmentInside OtherFragmentargs.screenTitle
HomeFragmentDirections<action> inside homeFragmentInside HomeFragmentactionHomeFragmentToOtherFragment(...)
NavController.navigate(@IdRes id)A destination or action IDAny navigation callernavigate(R.id.otherFragment)
NavController.navigate(direction)A Safe Args direction objectSource destinationnavigate(HomeFragmentDirections...)

Using raw IDs can work, but Safe Args directions provide compile-time checking for required arguments and their types. A manually built Bundle is flexible, but it depends on string keys and can fail at runtime if keys or types do not match.

Take Quiz

Cheat Sheet

  • <argument> on a destination generates DestinationNameArgs.
  • <action> inside a source destination generates SourceNameDirections.
  • Directions class names are plural: HomeFragmentDirections.
  • Define app:destination="@id/targetFragment" on an action.
  • Define argument types with app:argType, for example app:argType="string".
  • Read arguments in a Fragment with:
private val args: OtherFragmentArgs by navArgs()
  • Navigate with:
findNavController().navigate(
    HomeFragmentDirections.actionHomeFragmentToOtherFragment("Title")
)
  • A destination with only incoming actions may have an Args class but no Directions class.
  • Rebuild after changing the navigation graph if generated sources are not immediately visible.

FAQ

Why does Safe Args generate Args but not Directions?

Args is generated from destination arguments. Directions is generated from actions declared on a source destination. Add an <action> to generate a directions class.

Should I look for OtherFragmentDirection or OtherFragmentDirections?

Look for OtherFragmentDirections. The generated class name is plural.

Why is HomeFragmentDirections generated instead of OtherFragmentDirections?

The action is declared inside homeFragment. Directions APIs belong to the destination where navigation begins, not where it ends.

Can I navigate to a fragment without a directions class?

Yes. You can call navigate() with an action or destination ID and a Bundle. Directions are the safer option when you want typed generated APIs.

Does every fragment generate a Directions class?

No. A fragment generates a directions class only when it contains at least one outgoing <action>.

How do I pass an argument with a Safe Args action?

Declare the argument on the target fragment, then call the generated action method with the required value:

HomeFragmentDirections.actionHomeFragmentToOtherFragment("Hello")

What does app:argType do?

It declares the type of a navigation argument, such as string, integer, boolean, or a supported custom type. Safe Args uses it to generate a typed method parameter and typed argument reader.

Take Quiz

Mini Project

Description

Create a small two-screen app flow: a home screen opens a greeting screen and sends the name entered by the user. This demonstrates the complete Safe Args contract: an action at the source, an argument at the destination, generated directions for sending, and generated args for receiving.

Goal

Navigate from HomeFragment to GreetingFragment and display a greeting using a type-safe name argument.

Requirements

Add an action from HomeFragment to GreetingFragment. Declare a required string argument named name on GreetingFragment. Navigate when the user presses the greeting button. Pass the entered name through the generated directions method. Read the name with generated arguments and show it on the greeting screen.

Take Quiz

Keep learning

Related questions

View more