How to pass arguments to a fragment using bottom navigation view and Android Navigation component?
A

4

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
    ...
}
Adigranth answered 7/1, 2019 at 21:18 Comment(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?Dibasic
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.Adigranth
S
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.

Syman answered 8/1, 2019 at 6:55 Comment(4)
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 somethingAdigranth
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.Dibasic
I tried first approach, OnDestinationChangedListener is not working on very first time.Schwarzwald
@Dibasic 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?Wickedness
D
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.

Dibasic answered 8/1, 2019 at 17:18 Comment(5)
How can i receive these arguments in destination fragment?Fulviah
@NaumanAsh - how you receive the arguments, be it via Safe Args or not, is covered in the documentationDibasic
@Dibasic Can we change the argument value programmatically when an item is tapped from the bottomNavView?Orchestral
Where are these arguments populated from? The main activity?Calfee
@Calfee - the android:defaultValue means that this argument is always added, no matter how you navigate to that fragment.Dibasic
S
3

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)
Science answered 2/1, 2020 at 12:42 Comment(0)
E
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")
Enamelware answered 20/9, 2023 at 13:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.