androidkotlinnavigation-architecture

Navigation component arguments default value


In navigation component, While sending arguments from first fragment to second fragment, default values are not getting which set from navigation graph.

Here is my code:

navigation_graph.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/navigation_graph"
    app:startDestination="@id/firstFragment">


    <fragment
        android:id="@+id/firstFragment"
        android:name="com.example.navigationcomponent.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" >
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/nav_default_enter_anim" />

        <argument
            android:name="clickFrom"
            app:argType="string"
            android:defaultValue="From First Fragment" />
        <argument
            android:name="clickFragmentPosition"
            app:argType="integer"
            android:defaultValue="1" />

    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationcomponent.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />


</navigation>

FirstFragment:

class FirstFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val bundle = Bundle()
        bundle.putBoolean("IsFirstFragment", true)
        val navController = Navigation.findNavController(activity!!, R.id.my_nav_host_fragment)

        btnNext.setOnClickListener {
            navController.navigate(R.id.action_firstFragment_to_secondFragment, bundle)
        }
    }
}

SecondFragment:

class SecondFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val isFromFirstFragment = arguments?.getBoolean("IsFirstFragment", false)
        Log.d(TAG, "$isFromFirstFragment")
        Log.d(TAG, "${FirstFragmentArgs.fromBundle(arguments!!).clickFrom} ${FirstFragmentArgs.fromBundle(arguments!!).clickFragmentPosition}")

        val navController = Navigation.findNavController(activity!!, R.id.my_nav_host_fragment)
        btnBack.setOnClickListener {
            navController.navigateUp()
        }

        navController.addOnDestinationChangedListener { controller, destination, arguments ->
            Log.d("TAG", "${destination.label}");
        }
    }

    companion object {
        private const val TAG: String = "SecondFragment"
    }
}

Here while fetching default values in second fragment I am getting Null Pointer Exception

Log.d(TAG, "${FirstFragmentArgs.fromBundle(arguments!!).clickFrom} ${FirstFragmentArgs.fromBundle(arguments!!).clickFragmentPosition}")

My Question is, How can I get values of arguments set using navigation_graph.xml? Navigation Graph have auto-generated getter when you re-build the project. Is there any architecture to bind auto generated setters using default value?


Solution

  • If you want to send arguments from FirstFragment to SecondFragment, then you should replace your navigation_graph.xml with:

    <?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/navigation_graph"
        app:startDestination="@id/firstFragment">
    
    
        <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.navigationcomponent.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first" >
            <action
                android:id="@+id/action_firstFragment_to_secondFragment"
                app:destination="@id/secondFragment"
                app:enterAnim="@anim/nav_default_enter_anim" />
    
        </fragment>
    
        <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.navigationcomponent.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">
    
            <argument
                android:name="clickFrom"
                app:argType="string"
                android:defaultValue="From First Fragment" />
    
            <argument
                android:name="clickFragmentPosition"
                app:argType="integer"
                android:defaultValue="1" />
    
            <argument
                android:name="isFirstFragment"
                app:argType="boolean"
                android:defaultValue="false" />
    
        </fragment>
    
    
    </navigation>
    

    Then, you can pass arguments from your FirstFragment like this:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_first, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            val navController = Navigation.findNavController(activity!!, R.id.my_nav_host_fragment)
    
            btnNext.setOnClickListener {
                navController.navigate(
                    FirstFragmentDirections.actionFirstFragmentToSecondFragment( // this is an auto-generated class & method
                        // specify your arguments here: For example:
                        isFirstFragment = true,
                        clickFrom = "your argument here",
                        clickFragmentPosition = 1
                        // for default values, you can leave this blank
                    )
                )
            }
        }
    }
    

    Then to retrieve arguments in SecondFragment

    class SecondFragment : Fragment() {
    
        private val arguments: SecondFragmentArgs by navArgs() // add this line to retrieve arguments from navigation
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_second, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            val isFromFirstFragment = arguments.isFirstFragment
            Log.d(TAG, "$isFromFirstFragment")
            Log.d(TAG, "${arguments.clickFrom} ${arguments.clickFragmentPosition}")
    
            val navController = Navigation.findNavController(activity!!, R.id.my_nav_host_fragment)
            btnBack.setOnClickListener {
                navController.navigateUp()
            }
    
            navController.addOnDestinationChangedListener { controller, destination, arguments ->
                Log.d("TAG", "${destination.label}");
            }
        }
    
        companion object {
            private const val TAG: String = "SecondFragment"
        }
    }