3

I have implemented an argument to be passed between fragments in nav_graph, however when I attempt to set the argument in the originating fragment, the argument is not found by the NavDirections.

Note that Navigation works fine before trying to pass the argument.

If I do a Clean Project I lose the NavDirections. If I do a Rebuild I lose the argument.

Gradle:app

    //Navigation
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    apply plugin: "androidx.navigation.safeargs.kotlin"

nav_graph.xml

    <fragment
        android:id="@+id/destination_home"
        android:name="com.android.joncb.flightlogbook.HomeFragment"
        android:label="@string/lblHome"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_home_to_fltHistory"
            app:destination="@id/destination_fltHistory" />
        <action
            android:id="@+id/action_home_to_stats"
            app:destination="@id/destination_statistics" />
        <action
            android:id="@+id/action_home_to_newFlight"
            app:destination="@id/destination_newFlight" />
        <action
            android:id="@+id/action_home_to_fltDetails"
            app:destination="@id/destination_fltDetails" />
        <argument
            android:name="fltData"
            app:argType="string" />
    </fragment>

and in my Home Fragment I get the error "Unresolved reference: fltData"

        card_nextFlight.setOnClickListener {
            val actionDetails = HomeFragmentDirections.actionHomeToFltDetails()
            actionDetails.fltData ( flightData.toString())

            Navigation.findNavController(it).navigate(actionDetails)
        }

flightData is a data class

data class FlightDTO(
    var airlineName: String, var faCode: String, var fltNo: String, var aircraft: String,
    var depAP: String, var arrAP: String, var schedDep: String, var schedArr: String,
    var date: String, var leg: Int = 0, var actDep: String = "", var actArr: String = "" ){

...

    override fun toString(): String {

        return "$airlineName $faCode $fltNo $aircraft $depAP $schedDep $arrAP $schedDep $date"
    }
}

I want to pass the class ideally by making the class Parcelable, but until I can pass a string, there is no point venturing down the parcel line.

Zoe
  • 27,060
  • 21
  • 118
  • 148
jcbird
  • 31
  • 1
  • 8

4 Answers4

6

You are writing your XML wrong, think like this : The way I structure my XML properties is the way the generated code will look like and received between destinations sort of...

So basically in your nav_graph.xml you should change to:

<fragment
    android:id="@+id/destination_home"
    android:name="com.android.joncb.flightlogbook.HomeFragment"
    android:label="@string/lblHome"
    tools:layout="@layout/fragment_home">
    <action
        android:id="@+id/action_home_to_fltHistory"
        app:destination="@id/destination_fltHistory" />
    <action
        android:id="@+id/action_home_to_stats"
        app:destination="@id/destination_statistics" />
    <action
        android:id="@+id/action_home_to_newFlight"
        app:destination="@id/destination_newFlight" />
    <action
        android:id="@+id/action_home_to_fltDetails"
        app:destination="@id/destination_fltDetails">
        <argument
            android:name="fltData"
            app:argType="string" />
    </action>
</fragment>

and in your destination it should look something like:

<fragment
    android:id="@+id/destination_fltDetails"
    android:name="com.android.joncb.flightlogbook.FlightDetailsFragment"
    android:label="@string/lblFlightDetails"
    tools:layout="@layout/fragment_flight_details">
    <argument
        android:name="fltData"
        app:argType="string" />
</fragment>

and in your flight details fragment the properties are received by using:

private val args: FlightDetailsFragmentArgs by navArgs()
println(args.fltData) // prints the navigation data

UPDATE:

Forgot to mention your OnClickListener in your Home fragment that would look more like this:

card_nextFlight.setOnClickListener {
    val actionDetails = HomeFragmentDirections.actionHomeToFltDetails(flightData.toString())

    Navigation.findNavController(it).navigate(actionDetails)
}
Lucho
  • 1,455
  • 1
  • 13
  • 25
  • This is partially correct. You do not need to specify argument next to action, only argument inside declaration of destination is needed (aka destination_fltDetails here) – ror Apr 30 '20 at 08:08
  • @ror Well if you look at the structure of destinations and what data being provided in this case flight_details. If i understand you correct doing so would make every destination available with flight_details which would not go together with e.g. new_flight destination – Lucho Apr 30 '20 at 08:46
  • @ror or could you clarify :)? – Lucho Apr 30 '20 at 09:01
  • Thanks both ror and @Lucho. I have got it working, with a variation on both your comments.See the working solution below. – jcbird May 01 '20 at 04:23
  • @Lucho what I was trying to tell: 1. destination_home node must not have argument section 2. destination_fltdetails must have argument section but with package qualified name of FlightDTO 3. class FlightDTO must be serializable (implement interface Serializable). Upvoting your answer. – ror May 01 '20 at 08:38
1

For my case, I wrote a buggy code like that -

NavController navController = NavHostFragment.findNavController(this);
NavDirections navDirections = MyDestinationFragmentDirections.actionMyAction(myArgumentValue);

navController.navigate(navDirections.getActionId());

Then I change the last line into this -

 navController.navigate(navDirections);

And finally,it worked as expected!!!

The logic behind this was, in NavController class the method which accepting int (resId of action) always put null argument -

public void navigate(@IdRes int resId) {
        navigate(resId, null);
    }

So we should use -

 public void navigate(@NonNull NavDirections directions) {
        navigate(directions.getActionId(), directions.getArguments());
 }

method if we are willing to pass an arguments via an action.

Gk Mohammad Emon
  • 6,084
  • 3
  • 42
  • 42
0

my mistake was the following. I had something like

NavDirections action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment();

I changed to something like

ConfirmationAction action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment();
sucicf1
  • 335
  • 3
  • 6
-2

Rather than pass a data class, I have created a JSON String and passed a string

        card_nextFlight.setOnClickListener {
            val dataString = flightData.toJSONString()
            val actionDetails = HomeFragmentDirections.actionHomeToFltDetails(dataString)

            Navigation.findNavController(it).navigate(actionDetails)
        }

To get this to work I had to modify the actionHomeToFltDetails function to receive a string in HomeFragmentsDirections

    fun actionHomeToFltDetails(fltData: String): NavDirections = ActionHomeToFltDetails(fltData)
  }

I could not get @Lucho approach to handle the arg in the destination fragment to work so reverted to bundle management, and converted the JSON string back to a data class

        const val ARG_PARAM1 = "fltData"
.
.
.
        arguments?.let {
            argFltData = it.getString(ARG_PARAM1)

            Log.e("args","Passed Argument: $argFltData")

            fltData = gson.fromJson(argFltData, FlightDTO::class.java)

        }

Thanks again for your input and I hope this helps someone else through the drama.

jcbird
  • 31
  • 1
  • 8