Navigation Architecture Component- Passing argument data to the startDestination
S

13

101

I have an activity A that start activity B passing to it some intent data. Activity B host a navigation graph from the new Navigation Architecture Component.I want to pass that intent data to the startDestination fragment as argument how to do that?

Synonymous answered 14/5, 2018 at 15:59 Comment(2)
I'm pretty sure the navigation editor has options for "arguments" and then you can add the safe-args gradle plugin but the details are a bit hazy for me atmMaddalena
Yes i know but there is no way to pass arguments to the start destination fragmentSynonymous
S
46

OK, I found a solution to that problem thanks to Ian Lake from the Google team. Let say you have an activity A that will start activity B with some intent data and you want to get that data in the startDestination you have two options here if you using safe args which is my case you could do

StartFragmentArgs.fromBundle(requireActivity().intent?.extras)

to read the args from the Intent. If you don't use safe args you can extract the data from the bundle your self-using requireActivity().intent?.extras which will return a Bundle you can use instead of the fragment getArguments() method. That's it I try it and everything works fine.

Synonymous answered 22/5, 2018 at 11:32 Comment(9)
Where does StartFragmentArgs class comes from? Is it included in the navigation arch dependency?Clyde
It's a generated class by the navigation. If you are using safe argument. For example the fragment here called StartFragment so the generated arg class will be StartFragmentArgs. If the fragment called FooFragment the generated class will be FooFragmentArgs and so onSynonymous
I tried using it and added this line in onCreate of Activity StartFragmentArgs.fromBundle(requireActivity().intent?.extras) but argument's sent to my startDestination are nullToady
When you start the activity that has the graph you are passing the data as intent and extract it in requireActivity().intent?.extrasSynonymous
You need to use navigation safe args Gradle pluginSynonymous
Your answer is not complete. When you use SafeArgs to set the bundle it returns the class object itself setting all the bundle arguments. But you didn't told us how to get back that bundle in fragment. If we use fromBundle it ill return a new object always, so it won't have that value anymore when you try to get it and it will be nullUsually
You are my hero!Atropine
Thanks, saved my day :)Bracteate
This replaces the usual private val args: MyFragmentArgs by navArgs()Killion
H
41

I think this has changed again with the 1.0.0 release. And Google has hidden this information very well in the official documentation. Or at least I struggled to find it, but stumbled upon it in the Migrate to the Navigation component guide. How to pass arguments to the start destination is mentioned here: Pass activity destination args to a start destination fragment

In short

  1. You have to set the navigation graph programatically:
findNavController(R.id.main_content)
    .setGraph(R.navigation.product_detail_graph, intent.extras)
  1. Don't set the graph in the NavHostFragment XML declaration.
  2. Read the extras from the receiver side:
val args by navArgs<ProductDetailsArgs>()  
val productId = args.productId

Update: Google has said that the official documentation for passing arguments to the initial navigation target is indeed missing. Hopefully this is added soon as part of the Navigation component documentation.

Honeyhoneybee answered 27/3, 2019 at 9:37 Comment(2)
Hi @Johan Paul Can you please tell me what is R.id.main_content in this?Nihility
@Nihility It is the ID for the NavHostFragment where you would normally specify the navigation graph in the XML layout file.Honeyhoneybee
G
38

TLDR: You have to manually inflate the graph, add the keys/values to the defaultArgs, and set the graph on the navController.

Step 1

The documentation tells you to set the graph in the <fragment> tag in your Activity's layout. Something like:

<fragment
    android:id="@+id/navFragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:graph="@navigation/nav_whatever"
    app:defaultNavHost="true"
    />

REMOVE the line setting the graph=.

Step 2

In the Activity that will be displaying your NavHostFragment, inflate the graph like so:

val navHostFragment = navFragment as NavHostFragment
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_whatever)

Where navFragment is the id you gave your fragment in XML, as above.

Step 3 [Crucial!]

Create a bundle to hold the arguments you want to pass to your startDestination fragment and add it to the graph's default arguments:

val bundle = Bundle()
// ...add keys and values
graph.addDefaultArguments(bundle)

Step 4

Set the graph on the host's navController:

navHostFragment.navController.graph = graph
Gaze answered 21/5, 2018 at 23:13 Comment(3)
Your solution will work but quite complex i found a better way i will but in the other answerSynonymous
This solution is not working anymore in 1.0.0-alpha09. The addDefaultArgument function has been removed.Frumentaceous
addDefaultArgument has been removed from Navigation Architecture Component. Please have a look at my answer below to see the official recommended approach.Flight
P
32

It had been fixed in 1.0.0-alpha07. See detail.

The solution is similar to Elliot Schrock's answer, but wrapping by official API.

We have to manually inflate NavHostFragment or graph

Use

NavHostFragment.create(R.navigation.graph, args)

Or

navController.setGraph(R.navigation.graph, args)

The args are the data we want to pass to start destination.

Percent answered 26/12, 2018 at 2:48 Comment(0)
S
21

Following Pass data to the start destination section from official doc:

First, construct a Bundle that holds the data:

val bundle = Bundle().apply {
    putString(KEY, "value")
}

Next, use one of the following methods to pass the Bundle to the start destination:

  • If you're creating your NavHost programmatically

     NavHostFragment.create(R.navigation.graph, bundle)
    
  • Otherwise, you can set start destination arguments by calling one of the following overloads of NavController.setGraph():

     navHostFragment.navController.setGraph(R.navigation.graph, bundle)
    

Then you should use Fragment.getArguments() to retrieve the data in your start destination.

EDIT:

You can also use FragmentArgs instead of creating a bundle manually which makes it more convenient and type safe:

navHostFragment.navController.setGraph(R.navigation.graph, MyFragmentArgs(arg).toBundle())

Then in the fragment you can retrieve args as:

private val args: PodFragmentArgs by navArgs()

Make sure your fragment has argument element in the navigation.xml file:

<fragment
        android:id="@+id/myFragment"
        android:name="MyFragment"
        android:label="fragment_my"
        tools:layout="@layout/fragment_my">
        <argument
            android:name="argName"
            android:defaultValue="@null"
            app:argType="string"
            app:nullable="true" />
</fragment>
Spoor answered 19/3, 2020 at 17:45 Comment(2)
Link broken. Update the link if your remember.Yuan
@PankajKumar Link is updated, thanks for letting me know.Spoor
F
14

So finally, I have found a solution to this issue.

At the time of writing this answer, I am using Navigation 2.2.0-alpha01

If you want to pass some data to the start destination directly as arguments from host activity, you need to manually set your host’s navigation graph inside the host activity’s onCreate() method, as shown below:

Get you navController:

val navController by lazy { findNavController(R.id.<your_nav_host_id>) }

Then in the host activity's onCreate()

val bundle = Bundle()
bundle.putString("some_argument", "some_value")
navController.setGraph(R.navigation.<you_nav_graph_xml>, bundle)

Or if you want to pass the whole intent extras as it is to the startDestination:

navController.setGraph(R.navigation.<you_nav_graph_xml>, intent.extras)

Since intent.extras would return a Bundle only

When you are setting the navGraph using setGraph() method, you should avoid setting the app:NavGraph attribute in the NavHostFragment definition, because doing so results in inflating and setting the navigation graph twice.

While reading these arguments in your startDestination fragment:

If you are using the Safe Args Plugin (which is very much recommended), then in your fragment:

private val args by navArgs<DummyFragmentArgs>()

Safe Args plugin would generate an Args class by appending Args to your fragment name. For example, if you fragment is called DummyFragment then Safe Args would generate a class called DummyFragmentArgs

where navArgs<> is an extension function defined in Android KTX

If you are not using Android KTX, you can get the args object like:

val args = DummyFragmentArgs.fromBundle(arguments!!)

Once you've acquired the arguments object, you can simply fetch your arguments:

args.someArgument

Notice how we passed "some_argument" as argument, and we are reading it as someArgument using Safe Args

If you are not using Safe Args (there is no reason to not use it though), you can access your arguments like this:

arguments?.getString("some_argument")

All of this is documented in Migrate to Navigation Component documentation here: https://developer.android.com/guide/navigation/navigation-migrate#pass_activity_destination_args_to_a_start_destination_fragment

Flight answered 22/8, 2019 at 10:40 Comment(2)
can you give this solution in java?Kennithkennon
setGraph method is not working somehowEstheresthesia
C
3

addDefaultArguments is no more in the latest versions of library. I have fixed the problem like this:

        val navHostFragment = fragment_navigation_onboarding as NavHostFragment
        val navController = navHostFragment.navController
        val navInflater = navController.navInflater
        val graph:NavGraph = navInflater.inflate(R.navigation.navigation_your_xml)
        val model = Model()//you might get it from another activity
        graph.addArgument("Data", NavArgument.Builder().setDefaultValue(model).build()) // This is where you pass the bundle data from Activity to StartDestination
        navHostFragment.navController.graph = graph
Congenial answered 1/10, 2019 at 8:21 Comment(0)
U
2

After reading the solution i made one that suits for my needs, this solution assume that the data sent to the activity that host this graph

on the start destination:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // work around get get starting destination with activity bundle
    userId = getActivity().getIntent().getIntExtra(KEY_USER_ID, -1);
}
Unhealthy answered 16/3, 2020 at 15:50 Comment(0)
U
1

So for the people Still struggling with this. I found another way to do this without using Safe-Args and a step using @Elliot's Answer.

So lets say you received some arguments in Activity B from Activity A and Your Activity B has a fragment startDestination you are initialising the Nav controller like this:

navController = Navigation.findNavController(this, R.id.detailFragment);

from the Nav Controller you will have access to your graph which you have set in the XML like this and you can set the arguments in the defaultArguments:

navController.getGraph().addDefaultArguments(extras);

Note: This will also update values of the keys if it is already present in the graph xml

Now in your Fragment you have to find the default arguments from your NavHostFragment like this:

Bundle defaultArguments = NavHostFragment.findNavController(this).getGraph().getDefaultArguments();

and you will have the values there. I don't know why @Elliot thinks it's crucial but it should be the way?

UPDATE alpha09: addDefault argument is no longer supported in this version, You have to use NavArgument

Usually answered 10/12, 2018 at 13:50 Comment(0)
A
1

You can pass data to your app's start destination. First, you must explicitly construct a Bundle that holds the data. Next, use one of the following methods to pass the Bundle to the start destination you can set manually graph from the code and add argument with it and remove app:navGraph from xml and use this line of code in activity

navController.setGraph(R.navigation.graph, args)
Astronomical answered 1/9, 2021 at 20:44 Comment(0)
S
0

I've used answer from Elliot Schrock, but added bundle in different method

navController.setGraph(R.navigation.nav_graph, intent.extras!!)
Sweep answered 16/3, 2022 at 7:53 Comment(0)
C
0
class HadithActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hadith)
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
        val navController = navHostFragment.navController
        navController.setGraph(R.navigation.nav_hadith, intent.extras)
    }
}

activity_hadith.xml

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragmentContainerView"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_hadith" />
Cervicitis answered 17/9, 2022 at 10:5 Comment(0)
O
0

Using SafeArgs, follow these steps:

  1. In the Activity where the fragment is located, do not define the navGraph.
  2. In your navigation file, create an argument for your fragment with a default and nullable value

NOTE: Remember to define the bundle containing your data. Leave the argument name in the navGraph the same as the one used in the bundle

   <argument
    android:name="limit"
    app:argType="string"
    app:nullable="true"
    android:defaultValue="@null"/>
  1. Set the navGraph by your Activity:

private val controller by lazy { findNavController(R.id.nav_host_fragment) }

controller?.let { safeController ->
        val graph = safeController.navInflater.inflate(R.navigation.your_nav_graph)
        safeController.setGraph(graph, intent.extras) //or Bundle()
    }
  1. In your Fragment:
    private val args by navArgs<YourFragmentArgs>()
    val argument = args.yourArgument
Orvilleorwell answered 18/1 at 15:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.