38

How does one pass argument(s) to a nested Navigation architecture component graph?

Let's say I construct my navigation graph to navigate from FragmentA --> Nested, where Nested contains FragmentB --> FragmentC...

If this was a pure FragmentA --> FragmentB... graph, I would just set up the navigation with FragmentADirections.actionFragmentAToFragmentB(argument = foo). But that action takes zero arguments as soon as you turn B --> C into Nested...

So what am I supposed to do?

Algar
  • 5,734
  • 3
  • 34
  • 51

4 Answers4

99

Global actions might be a way but I didn't get that working as I wanted once I extracted the nested graph to its own .xml. But it turned out to be embarrassing simple - just add the arguments manually in code, to your action.

An example related to the question would be:

Save the nested graph to nested_graph.xml, it will look something like

<navigation
    android:id="@+id/nested_graph"
    app:startDestination="@id/fragmentB"
    ...>

    <fragment 
        android:id="@+id/fragmentB"
        ...>
        <argument
            android:name="foo"
            app:argType="integer"/>
        <action 
            ... // navigation action to FragmentC />
    </fragment>

    <fragment ...  // FragmentC stuff
</navigation>

To pass arguments to nested_graph.xml from a different graph, say root_graph.xml do

<navigation
    android:id="@+id/root_graph"
    app:startDestination="@id/fragmentA"
    ...>

    <fragment 
        android:id="@+id/fragmentA"
        ... >
        <action
            android:id="@+id/action_fragmentA_to_nested_graph"
            app:destination="@id/nested_graph">
            <argument
                android:name="foo"
                app:argType="integer"/>
        </action>
    </fragment>
    <include app:graph="@navigation/nested_graph"/>
</navigation>

In other words, just add the same <argument ... /> to the root_graph action as you expect to receive in the nested_graph.

Algar
  • 5,734
  • 3
  • 34
  • 51
  • 4
    Agreed this seems like the only workaround for now. There is a feature request for improving this: https://issuetracker.google.com/issues/109505019 – Carson Holzheimer Mar 05 '19 at 05:22
  • embaressingly easy :)) they are the ones to be embraced that disabled it in attributes editor window :/ – Amin Keshavarzian Jul 17 '19 at 11:05
  • 1
    Is there a logical reason this functionality wouldn't be documented/or available in the nav editor? What I mean is, is it assumed we would instead store the values that need to be passed in a shared view model or something like that? It seems awkward that we need a SO post to explain this simple functionality. –  Feb 13 '20 at 08:31
  • Thanks for this, however does safeargs work with this? I'm not able to find a generated class holding the args. – Zach Sperske Oct 02 '20 at 18:11
  • @ZachSperske It sure does. I guess you will have to debug a bit more :) – Algar Oct 06 '20 at 11:45
  • I guess the reason why this is not documented anywhere is that technically this makes `SafeArgs` no longer safe because there is more than one source of truth now. The `argument` in source `action` defines code to be generated in `SourceFragmentDirections` and the `argument` in destination `fragment` defines code to be generated in `DestinationFragmentArgs`. If you update arguments in the destination but forget to update the source, the app will compile just fine but will crash at runtime. This is a poor solution for large applications with multiple SourceFragments in multiple modules... – Antimonit Oct 23 '20 at 08:23
  • But what if I want to start FtragmenC with args from root graph? – bitvale Jul 20 '21 at 12:12
0

actully it's very simple you just need to add your arguments in nested_nav for example

 <navigation
        android:id="@+id/root_graph"
        app:startDestination="@id/fragmentA"
            ...>

            <fragment
                android:id="@+id/fragmentA"
            ... >
            <action
                android:id="@+id/action_fragmentA_to_nested_graph"
                app:destination="@id/nested_graph">
            </action>
        </fragment>
        <navigation
            android:id="@+id/nested_graph"
            app:startDestination="@id/fragmentB"
                ...>
            <argument
                android:name="foo"
                app:argType="integer"/>
            <fragment
            android:id="@+id/fragmentB"
                ...>
            <argument
            android:name="foo"
            app:argType="integer"/>
            <action
                ... // navigation action to FragmentC />
                </fragment>
            
            <fragment ...  // FragmentC stuff
        </navigation>
    </navigation>

or you can send your data with a bundle

ilyas ipek
  • 117
  • 2
  • 5
-1

If you do not want to create a separated xml for the nested graph you can override destination argument in an action as android developers says here. I just test it for use with navigation graph view model scope and it worked perfectly. I am using Version 2.2.0-alpha03 of navigation component. After added those params to the action action_inboxFragment_to_conversation_graph, now the InboxFragmentDirections.ActionInboxFragmentToConversationGraph is generated correctly.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/messages_graph"
    app:startDestination="@id/inboxFragment">
    <fragment
        android:id="@+id/inboxFragment"
        android:name="com.wlpr.docfinder.ui.fragment.InboxFragment"
        android:label="fragment_inbox"
        tools:layout="@layout/fragment_inbox" >
        <action
            android:id="@+id/action_inboxFragment_to_conversation_graph"
            app:destination="@id/conversation_graph">
            <argument
                android:name="participantName"
                android:defaultValue="Conversation"
                app:argType="string"
                app:nullable="true" />
            <argument
                android:name="documentsCount"
                android:defaultValue="1"
                app:argType="integer" />
        </action>
    </fragment>
    <navigation
        android:id="@+id/conversation_graph"
        android:label="conversationGraph"
        app:startDestination="@id/conversationFragment">
        <fragment
            android:id="@+id/conversationFragment"
            android:name="com.wlpr.docfinder.ui.fragment.ConversationFragment"
            android:label="fragment_conversation"
            tools:layout="@layout/fragment_conversation">
            <action
                android:id="@+id/action_conversationFragment_to_reportingDetailsFragment"
                app:destination="@id/reportingDetailsFragment" />
            <argument
                android:name="participantName"
                android:defaultValue="Conversation"
                app:argType="string"
                app:nullable="true" />
            <argument
                android:name="documentsCount"
                android:defaultValue="1"
                app:argType="integer" />
        </fragment>
        <!-------- more fragments... -------->
</navigation>

wilmerlpr
  • 448
  • 6
  • 15
-1

First Approach:

  1. In Included Graph inside of the <fragment> block of IncludedFragment add the destination to the @id/included_fragment(itself):
<action
    android:id="@+id/open_included_fragment"
    app:destination="@+id/included_fragment"/>
  1. In the Main Graph make sure that your desired <action> id is the same as the id of the <action> in Included Graph.

Overall your code should be like this:

Main Graph

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_graph"
    app:startDestination="@+id/main_fragment">
    
    <include app:graph="@navigation/included_graph"/>

    <fragment
         android:id="@+id/main_fragment"
         android:name="com......MainFragment">

        <action
            android:id="@+id/open_included_fragment"
            app:destination="@+id/included_graph"/>
    </fragment>

</navigation>

Included Graph

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/included_graph"
    app:startDestination="@+id/included_fragment">
    
    <fragment
        android:id="@+id/included_fragment"
        android:name="com......IncludedFragment">

        <action
            android:id="@+id/open_included_fragment"
            app:destination="+@id/included_fragment"/>

        <argument
            android:name="some_argument"
            app:argType="integer" />
    </fragment>

</navigation>

Then, to navigate to the IncludedFragment, you should use IncludedFragmentDirections class:

findNavController().navigate(
    IncludedFragmentDirections.openIncludedFragment(69)
)

Second Approach:

  1. Use the same Main Graph navigation graph XML as in the First Approach.
  2. Included Graph should be like this:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/included_graph"
    app:startDestination="@+id/included_fragment">
    
    <fragment
        android:id="@+id/included_fragment"
        android:name="com......IncludedFragment">

        <argument
            android:name="some_argument"
            app:argType="integer" />
    </fragment>

</navigation>
  1. Create an extended NavDirections class to allow the passing of the arguments (because the default generated one is ActionOnlyNavDirections without arguments):
class ActionArgumentsNavDirections(
    override val actionId: Int,
    override val arguments: Bundle
) : NavDirections
  1. Use ActionArgumentsNavDirections in the navigation method, where actionId equals to R.id.open_included_fragment, and arguments should be initialized with the help of IncludedFragmentArgs class, like this:
findNavController().navigate(
    ActionArgumentsNavDirections(
        actionId = R.id.open_included_fragment,
        arguments = IncludedFragmentArgs(69).toBundle()
    )
)
GIGAMOLE
  • 1,274
  • 1
  • 11
  • 17