'Android Navigation Component : Pass value (arguments) in fragments

What I have done:

I have created Navigation Drawer Activity, As updated new format of Navigation Drawer Activity, As per new Android architecture, I got it with Navigation Component structure.

The NavigationView code with NavController and NavigationUI is below which is opening fragment when I click on any navigation item.

    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    NavigationView navigationView = findViewById(R.id.nav_view);
    // Passing each menu ID as a set of Ids because each
    // menu should be considered as top level destinations.
    mAppBarConfiguration = new AppBarConfiguration.Builder(
            R.id.nav_home, R.id.nav_profile, R.id.nav_privacy_policy,
            R.id.nav_terms, R.id.nav_contact_us, R.id.nav_share, R.id.nav_send)
            .setDrawerLayout(drawer)
            .build();
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, navController);

This is for nav_host_fragment:

<fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

The navigation is happening using this navigation/mobile_navigation.xml

<?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/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="com.sohamerp.marsremedies.fragment.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_profile"
        android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
        android:label="@string/menu_my_profile"
        tools:layout="@layout/fragment_profile" />

    <fragment
        android:id="@+id/nav_privacy_policy"
        android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
        android:label="@string/menu_privacy_policy"
        tools:layout="@layout/fragment_privacy_policy" />

    <fragment
        android:id="@+id/nav_terms"
        android:name="com.sohamerp.marsremedies.fragment.TermsConditionFragment"
        android:label="@string/menu_terms"
        tools:layout="@layout/fragment_terms_condition" />

    <fragment
        android:id="@+id/nav_contact_us"
        android:name="com.sohamerp.marsremedies.fragment.ContactUsFragment"
        android:label="@string/menu_contact_us"
        tools:layout="@layout/fragment_terms_condition" />

</navigation>

What I want to do:

Now I want to pass some values as a bundle (arguments) in Fragment when it's called.

Scenario: I have two fragments PrivacyPolicyFragment and TermsConditionsFragment, In both fragments, I am just opening links inside WebView accordingly. So When I click on the menu item of Privacy Policy, I will pass a link related to the same.

In this new structure navigation/mobile_navigation.xml opening fragments, How can I pass arguments?

Any help?



Solution 1:[1]

So I forgot to go through this link : Define Destination Arguments

But this answer helpful to all lazy peoples like me:

Add dependency in project level build.gradle:

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0"

Apply plugin in app level build.gradle:

apply plugin: "androidx.navigation.safeargs"

Using XML: predefined (static) value:

In xml file of navigation /navigation/mobile_navigation.xml declare argument tag as below or you can design through this link:

<fragment
    android:id="@+id/nav_privacy_policy"
    android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
    android:label="@string/menu_privacy_policy"
    tools:layout="@layout/fragment_privacy_policy" >
    <argument
        android:name="privacyPolicyLink"
        app:argType="string"
        android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>

<fragment
    android:id="@+id/nav_terms"
    android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
    android:label="@string/menu_terms"
    tools:layout="@layout/fragment_terms_condition" >
    <argument
        android:name="privacyPolicyLink"
        app:argType="string"
        android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>

Now you have to write code in your Fragment like:

if(getArguments() != null) {
    // The getPrivacyPolicyLink() method will be created automatically.
    String url = PrivacyPolicyFragmentArgs.fromBundle(getArguments()).getPrivacyPolicyLink();
}

Hope it will helps you others.

Solution 2:[2]

To pass arguments to other Fragments/Destinations, use Safe Args which ensures type safety. Just like @bromden illustrated, Safe Args will generate a class for each fragment/destination where an action originates. You can then pass the arguments into the action that navigates to the Fragments.

In the receiving fragment, say PrivacyFragment if your code is in Kotlin, use by navArgs() property delegate to access the arguments. i.e.

val args: PrivacyFragmentArgs by navArgs()

To better understand this, visit Pass data between destinations

Solution 3:[3]

In this scenario, you can use

  private NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
  // Create the Bundle to pass, you can put String, Integer, or serializable object
  Bundle bundle = new Bundle();
  bundle.putString("link","http://yourlink.com/policy");
  bundle.putSerializable("USER", user); // Serializable Object
  navController.navigate(R.id.nav_terms, bundle); // called fragment with agruments

In case of any help you can reply on it

Solution 4:[4]

Simple and fast solution:

pass arguments between destinations

Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);

and receiving

TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));

Solution 5:[5]

You could implement NavigationView.OnNavigationItemSelectedListener And do something like this:

 override fun onNavigationItemSelected(item: MenuItem): Boolean {
   drawer_layout.closeDrawers()

        if (item.itemId == nv_navigation_drawer_navigation_view.checkedItem?.itemId)
            return false

     Handler().postDelayed({
                when (item.itemId) {
                    R.id.nav_privacy_policy -> {
                           val action = FragmentDirections.actionFragmentToPrivacyFragment("Policy link")

                     findNavController().navigate(action)

                    }
                }
            }, DRAWER_NAVIGATION_DELAY)
  return true
    }

And in xml you can add argument to the recieving fragment, in this case

   <fragment
    android:id="@+id/nav_privacy_policy"
    android:name=".fragment.PrivacyPolicyFragment"
    android:label="@string/menu_privacy_policy"
    tools:layout="@layout/fragment_privacy_policy">

    <argument
        android:name="policy"
        app:argType="string" />
</fragment>

Solution 6:[6]

In newer version of Android Studio 3.2+, below dependency and plug-in need to add in both build.gradle file

Step-1

Add dependency in Project-Level build.gradle

dependencies {
    classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
}

Apply plugins in App-Level build.gradle

plugins {
    id 'androidx.navigation.safeargs' 
}

Step-2

  1. In Navigation file, res/navigation/nav_graph.xml
  2. Declare argument tag in any fragment or inner fragment with action tag
  3. List item

Sample xml code

 <fragment
     android:id="@+id/nav_register"
     android:name="com.pd.demo.ui.profile.RegisterFragment"
     android:label="@string/title_register"
     tools:layout="@layout/fragment_register">
     <action
         android:id="@+id/action_nav_register_to_nav_verify_otp"
         app:destination="@id/nav_verify_otp">
         <argument
             android:name="mobile"
             app:argType="string" />
         <argument
             android:name="password"
             app:argType="string" />
     </action>
 </fragment>

Step-3

Below Kotlin code, pass argument to destination fragment

val bundle = bundleOf("mobile" to binding.etMobileNo.text.toString().trim())
Navigation.findNavController(binding.root).navigate(R.id.action_nav_register_to_nav_verify_otp, bundle)

Step-4

Below Kotlin code, get bundle argument from source fragment

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    mobileNo = arguments!!.getString("mobile").toString()
    password = arguments!!.getString("password").toString()
}

This code will helps

Solution 7:[7]

You can also pass serializable objects, enum values and arrays of primitive types. For example:

enum class ObjectType : Serializable {
    FIRST, SECOND
}

Then, add arguments to the xml

<fragment
        android:id="@+id/nav_profile"
        android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
        android:label="@string/menu_my_profile"
        tools:layout="@layout/fragment_profile" >

        <argument
            android:name="myObjectType"
            android:defaultValue="SECOND"
            app:argType="com.project.app.data.ObjectType" />
</fragment>

Note, that you should specify complete path!

Solution 8:[8]

Passing data from the start destination with NavController NavGraph navigate is straightforward. I use this to display order lines associated to an order header:

    private void showRepositionLinesFragment(AppObjects.RepOrderHeader orderHeader) {

    int number = orderHeader.getOrderNumber();
    String orderNumber = String.format("%06d",number);
    String createDate = orderHeader.getCreateDate();
    Globals.LogTrace(this, AppAlertDialog.DialogType.Info,
        "Navigate to FragRepoLines with orderNumber: " + orderNumber,false);
    NavController navController = NavHostFragment.findNavController(FragmentRepositionHeaders.this);
    Bundle bundle = new Bundle();
    bundle.putString(getString(R.string.arg_header_ordernumber),orderNumber);
    bundle.putString(getString(R.string.arg_repheader_createdate), createDate);
    navController.getGraph().findNode(R.id.FragRepoLines).setLabel(orderNumber + " " + createDate);
    navController.navigate(R.id.action_FragRepoHeaders_to_FragRepoLines,bundle);
}

Getting data from the fragment that handles the order lines turned to be more complicated. Tried for hours with NavController getArguments(). In the end this is what worked for me.

In the start fragment:

    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
        .getSavedStateHandle()
        .getLiveData(getString(R.string.arg_header_ordernumber));
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            Globals.LogTrace(this, AppAlertDialog.DialogType.Info, "+++++++++  liveData changed -> " + s, false);
        }
    });

In the destination fragment:

    String arg = getString(R.string.arg_header_ordernumber);
    NavController navController = NavHostFragment.findNavController(this);
    NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry();
    if (navBackStackEntry != null) {
        SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
        if (savedStateHandle != null) {
            savedStateHandle.set(arg, "000000");
        } else {
            Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"savedStateHandle == null",false);
        }
    } else {
        Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"navBackStackEntry == null",false);
    }

Source: Interact programmatically with the Navigation component

I changed the navController.getPreviousBackStackEntry() for navController.getCurrentBackStackEntry()

Solution 9:[9]

I had the same issue but I´m still not able to pass the arguments using fragment directions. Since I need the value in several of my fragments I decided to use a companion object in my main activity. It´s probably not the best but it solves the problem: class MainActivity : AppCompatActivity() {

    companion object{
        var myGlobalVar = "Example"
    }

override fun onCreate(savedInstanceState: Bundle?) {.... 

Then I can access its value in all of my fragments by importing it:

import myAppPackage.MainActivity.Companion.myGlobalVar

I had to delete the argument from my navGraph but i can still access it in the background.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pratik Butani
Solution 2 Mayokun
Solution 3 Vivek Hande
Solution 4 sina akbary
Solution 5 bromden
Solution 6 Pratik Dodiya
Solution 7 Dievskiy
Solution 8 loonighan
Solution 9 tuti