Android SafeArgs generated action is missing arguments
Asked Answered
C

4

12

My project uses SafeArgs. I've switched to a branch that someone else created, and building the project generates a compiler error, because the generated "~Directions" class' methods for return ActionOnlyNavDirections (no arguments passed to destination fragment) even though in the nav_graph, the fragment takes an argument.

For example, with the following nav_graph.xml:

<fragment
    android:id="@+id/fragment_a"
    android:name="com.myapp.ui.fragment.FragmentA"
    android:label="Fragment A"
    tools:layout="@layout/fragment_a">

<argument
        android:name="userName"
        android:defaultValue=" "
        app:argType="string" />
    <action
        android:id="@+id/action_fragment_a_to_fragmentX"
        app:destination="@id/fragmentX" />

    <action
        android:id="@+id/action_fragment_a_to_homeFragment"
        app:destination="@id/homeFragment" />

    <action
        android:id="@+id/action_fragmentA_to_fragmentC"
        app:destination="@id/fragmentC"
        app:popUpTo="@id/loginFragment" />

</fragment>

<fragment
    android:id="@+id/fragment_b"
    android:name="com.myapp.ui.fragment.FragmentB"
    android:label="Fragment B"
    tools:layout="@layout/fragment_b">

    <argument
        android:name="from"
        app:argType="com.myapp.data.local.model.ToFragmentBFrom"/>

    <action
        android:id="@+id/action_fragmentB_to_homeFragment"
        app:destination="@id/homeFragment" />

    <action
        android:id="@+id/action_fragmentB_to_fragmentC"
        app:destination="@id/fragmentC"
        app:popUpTo="@id/homeFragment" />

</fragment>

<fragment
    android:id="@+id/fragment_c"
    android:name="com.myapp.fragment.fragmentC"
    android:label="Fragment C"
    tools:layout="@layout/fragment_c">

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

I wind up with the following Directions classes:

class FragmentADirections private constructor() {
    private data class ActionFragmentAToFragmentC(val userName: String) : NavDirections {
        override fun getActionId(): Int = R.id.action_fragmentA_to_fragmentC

        override fun getArguments(): Bundle {
            val result = Bundle()
            result.putString("userName", this.userName)
            return result
        }
    }

    companion object {
        fun actionFragmentAToFragmentX(): NavDirections =
                ActionOnlyNavDirections(R.id.action_fragmentA_to_fragmentX)

        fun actionFragmentAToHomeFragment(): NavDirections =
                ActionOnlyNavDirections(R.id.action_fragmentA_to_homeFragment)

        fun actionFragmentAToFragmentC(userName: String): NavDirections =
                ActionFragmentAToFragmentC(userName)

        fun actionGlobalFragmentA(userName: String = " "): NavDirections =
                NavGraphDirections.actionGlobalFragmentA(userName)


    }
}

and:

class FragmentBDirections private constructor() {
    companion object {
        fun actionFragmentBToHomeFragment(): NavDirections =
                ActionOnlyNavDirections(R.id.action_fragmentA_to_homeFragment)

        fun actionFragmentBToFragmentC(): NavDirections =
                ActionOnlyNavDirections(R.id.action_fragmentB_to_fragmentC)
    }
}

AS you can see, FragmentC takes a "userName" argument, and the actionFragmentAToFragmentC respects this, whereas actionFragmentBToFragmentC does not. I've tried cleaning, manually deleting the build folder, invalidating cache and restarting, and rebuilding but still the generated classes always look the same. Why is SafeArgs generating arguments for one Directions class and not the other?

How can I debug the SafeArgs plugin at build time to learn more about what's causing this?

Cannabis answered 30/12, 2019 at 21:22 Comment(0)
M
3

I changed from "safeargs" to "safeargs.kotlin" and it worked. Although the Docs said "safeargs" should work on both Java and Kotlin.

plugins {
  // id 'androidx.navigation.safeargs' 
  id 'androidx.navigation.safeargs.kotlin'
}
Merill answered 22/7, 2021 at 7:50 Comment(0)
M
2

This sounds and feels a little bit awkward, since the generated FragmentBDirections.class should contain FragmentC's arguments automatically. From time to time I face this problem as well, but I can't tell you why the safe args plugin behaves this way and especially why it doesn't behaves always similary.

To fix this, add the <argument> explicitly in the <action>.

<action
    android:id="@+id/action_fragmentB_to_fragmentC"
    app:destination="@id/fragmentC"
    app:popUpTo="@id/homeFragment" />
    
    <argument
        android:name="userName"
        app:argType="String" />
</action>

For sure, this produces some code overhead in the NavGraph since you need to specify the arguments always in the <fragment> and the <action>. This does not seam to be the "cleanest" solution. But this works for me always and I can still profit of the SafeArgs.

Mucosa answered 3/11, 2020 at 17:1 Comment(0)
O
1

For me this was caused by having my source and destination fragment in the same xml file but under different navigation elements. Moving both fragments under the same navigation element generated the arguments

I changed from this (with a nested navigation element)

<navigation 
    ...
    android:id="@+id/nav_main">

    <fragment
        android:id="@+id/fragment_a"
        android:name="com.myapp.ui.fragment.FragmentA"
        android:label="Fragment A"
        tools:layout="@layout/fragment_a">
        <action
             android:id="@+id/action_fragment_a_to_fragment_b"
            app:destination="@id/fragment_b" />
    </fragment>

    <navigation 
        ...
        android:id="@+id/nav_other">
        <fragment
            android:id="@+id/fragment_b"
            android:name="com.myapp.ui.fragment.FragmentB"
            android:label="Fragment B"
            tools:layout="@layout/fragment_b">

            <argument
                android:name="from"
                app:argType="com.myapp.data.local.model.ToFragmentBFrom"/>
        </fragment>
    </navigation>
</navigation>

To this

<navigation 
    ...
    android:id="@+id/nav_main">

    <fragment
        android:id="@+id/fragment_a"
        android:name="com.myapp.ui.fragment.FragmentA"
        android:label="Fragment A"
        tools:layout="@layout/fragment_a">
        <action
             android:id="@+id/action_fragment_a_to_fragment_b"
            app:destination="@id/fragment_b" />
    </fragment>

    <fragment
        android:id="@+id/fragment_b"
        android:name="com.myapp.ui.fragment.FragmentB"
        android:label="Fragment B"
        tools:layout="@layout/fragment_b">

        <argument
            android:name="from"
            app:argType="com.myapp.data.local.model.ToFragmentBFrom"/>
    </fragment>
</navigation>

It seems as though you may need to double-define arguments (both in the destination and in the argument) when navigating across navigation elements or across entire graphs although I don't see any specific mention of this in the documentation

Opal answered 9/3, 2021 at 16:53 Comment(0)
J
0

Default value is defined in FragmentA as

android:defaultValue=" "

but not in FragmentB. Navigation component doesn't know what to assign to non nullable String by default.

Try doing the same in fragment B.

Jessen answered 29/7, 2021 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.