25

Is it possible to pass and access arguments in a fragment using a bottom navigation view and the Navigation component?

I'm using a one activity with many fragments approach where my top level fragment requires an argument(Usually done via the newInstance generated method). I've had a look at the Navigation component developer guide and the codelab but it only mentions using safeargs and adding argument tags in the destinations and actions.

Here's my navigation graph:

<navigation xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" 
    app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment"
          android:name="uk.co.homeready.homeready.HomeFragment"
          android:label="fragment_home"
          tools:layout="@layout/fragment_home">
          <!--Do I create an argument block here?-->
    </fragment>

    <fragment android:id="@+id/calculatorFragment"
          android:name="uk.co.homeready.homeready.CalculatorFragment"
          android:label="fragment_calculator"
          tools:layout="@layout/fragment_calculator"/>

    <fragment android:id="@+id/resourcesFragment"
          android:name="uk.co.homeready.homeready.ResourcesFragment"
          android:label="fragment_resources"
          tools:layout="@layout/fragment_resources"/>

</navigation>

Bottom Navigation View menu:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home"/>

    <item
        android:id="@+id/calculatorFragment"
        android:icon="@drawable/ic_baseline_attach_money_24px"
        android:title="@string/title_calculator"/>

    <item
        android:id="@+id/resourcesFragment"
        android:icon="@drawable/ic_baseline_library_books_24px"
        android:title="@string/title_resources"/>

</menu>

MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val navController = Navigation.findNavController(this, 
        R.id.nav_host_fragment)
        bottom_navigation.setupWithNavController(navController)
        ....
}

activity_main.xml

<android.support.constraint.ConstraintLayout>
    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"/>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        app:menu="@menu/bottom_navigation"/>

</android.support.constraint.ConstraintLayout>

HomeFragment

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val argument = //TODO access argument here
    ...
}
2
  • So you want your homeFragment to be started with some default set of arguments when you tap on it from the bottom nav? Are you reusing the homeFragment somewhere else with different arguments then or are they always just fixed values? Commented Jan 7, 2019 at 23:10
  • Yes for my usecase I would like to pass some default set arguments when you tap the bottom nav. I would also possibly reuse homeFragment somewhere else too. Commented Jan 8, 2019 at 10:55

4 Answers 4

32

If I understood you correctly, you want to pass arguments to destinations that is tied to menu items. Try to use 'OnDestinationChangedListener' inside your activity onCreate method, something like this:

navController.addOnDestinationChangedListener { controller, destination, arguments ->
        when(destination.id) {
            R.id.homeFragment -> {
                val argument = NavArgument.Builder().setDefaultValue(6).build()
                destination.addArgument("Argument", argument)
            }
        }
    }

Update:

If you want that your start destination will receive default arguments the implementation should be different. First, remove 'app:navGraph="@navigation/nav_graph"' from your 'NavHostFragment' xml tag.

Then, inside your activity onCreate you need to inflate the graph:

 val navInflater = navController.navInflater
 val graph = navInflater.inflate(R.navigation.nav_graph)

Then add your arguments to graph(this arguments will be attached to start destination)

val navArgument1=NavArgument.Builder().setDefaultValue(1).build()           
val navArgument2=NavArgument.Builder().setDefaultValue("Hello").build()
graph.addArgument("Key1",navArgument1)
graph.addArgument("Key2",navArgument2)

Then attach the graph to NavController:

navController.graph=graph

Now your first destination should receive the attached arguments.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the answer Alex. I'm retrieving the the argument in the HomeFragment via val argument = arguments?.getString("Argument") However on app start up argument is null in HomeFragment even though I can see that the listener is being called and the argument is being created in the main activity. I have to tap on another item in the bottom nav and then tap and navigate back to HomeFragment in order for argument to return something
The reason why the OnDestinationChangedListener only works the second time is because that is called after the navigate() call is done and the arguments are set for the first Fragment. Adding the <argument> to the graph is the exact equivalent to manually creating a NavArgument at runtime and is the recommended approach.
I tried first approach, OnDestinationChangedListener is not working on very first time.
@ianhanniballake I'm having the same problem where OnDestinationChangedListener doesn't work the first time. I added my argument to the destination through xml aswell, but it still didn't work. Any ideas as to why?
7

The correct way to do this is indeed with an <argument> block on your destination.

<fragment android:id="@+id/homeFragment"
      android:name="uk.co.homeready.homeready.HomeFragment"
      android:label="fragment_home"
      tools:layout="@layout/fragment_home">
      <argument
          android:name="Argument"
          android:defaultValue="value"
          />
</fragment>

This will automatically populate the arguments of the Fragment with the default value without any additional code needed. As of Navigation 1.0.0-alpha09, this is true whether you use the Safe Args Gradle Plugin or not.

5 Comments

How can i receive these arguments in destination fragment?
@NaumanAsh - how you receive the arguments, be it via Safe Args or not, is covered in the documentation
@ianhanniballake Can we change the argument value programmatically when an item is tapped from the bottomNavView?
Where are these arguments populated from? The main activity?
@Mattwmaster58 - the android:defaultValue means that this argument is always added, no matter how you navigate to that fragment.
4

Default values was not usable for me, because I have dynamic menu items that could have multiple of the same destination with different arguments. (changed from server)

Implement BottomNavigationView.OnNavigationItemSelectedListener:

override fun onNavigationItemSelected(item: MenuItem): Boolean {
    val fragmentId = item.itemId
    val arguments = argumentsByFragmentId[fragmentId] // custom mutableMapOf<Int, Bundle?>() with arguments
    navController().navigate(fragmentId, arguments)
    return true
}

To use that you will takeover the navigation, by replacing the listener. The order of calls here are important:

bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.setOnNavigationItemSelectedListener(this)

Comments

0

My argument was already defined in the nav graph xml and not sure why but @Alex's answer didn't work for me, instead I did this (from the destinationChangedListener):

 arguments?.putParcelable("argname", argvalue)

Note I was using this to give a specific response when deep linking to a particular nav bar tab. By default I needed to also do this (to avoid the same bundle getting re-used and deep link handling happening again):

arguments?.remove("argname")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.