Pass arguments safely to nested graph with Navigation Component - android

I'm using Android Jetpack Navigation Component.
I have a nested nav graph with id, say R.id.nested_graph
The first Fragment of the nested graph receives one parameter.
<navigation
android:id="#+id/nested_graph"
android:label="Nested Graph"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="...."
android:label="....">
<argument
android:name="item_id"
app:argType="integer" />
</fragment>
[...]
</navigation>
How can I pass the parameter to the nested graph using safe args?
At the moment, I need to pass the argument manually in the bundle, using the API that receives the id of the nested graph directly:
val args = Bundle()
args.putInt("item_id", itemId)
navController.navigate(R.id.nested_graph, args)
I'd like to use safe args, and do something like:
val directions = OrigininFragmentDirections.nestedGraph(itemId)
navController.navigate(directions)
But when trying that, I get the following error at build time:
Too many arguments for public final fun nestedGraph(): NavDirections defined
The issue is that the nav graph preprocessing is generating the factory method to create the NavDirections object without the required parameter in the signature.
The declaration of the nested graph looks like this:

After some trial and error experimentation (I don't think it's officially documented by Google, or at least I could not find it), I've discovered that navigating to nested nav graphs passing arguments safely can be done:
You need to add the argument XML object the first fragment expects in the root of the nested fragment itself.
In my case, the fragment with id firstFragment, which is the first fragment in the nested graph receives:
<argument
android:name="item_id"
app:argType="integer" />
Hence, I need to add that argument to the nested graph:
<navigation
android:id="#+id/nested_graph"
android:label="Nested Graph"
app:startDestination="#id/firstFragment">
<argument
android:name="item_id"
app:argType="integer" />
<fragment
android:id="#+id/firstFragment"
android:name="...."
android:label="....">
<argument
android:name="item_id"
app:argType="integer" />
</fragment>
[...]
</navigation>
Now I can navigate to it with:
val directions = OrigininFragmentDirections.nestedGraph(itemId)
navController.navigate(directions)
Note that the navigation graph editor does not do it for you. This needs to be done manually in the XML code.

#GaRRaPeTa answer is almost correct, but if you navigate from main graph to nested graph by action using SafeArgs, you must also add an argument to the action:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/graph_main"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="com.example.MainFragment">
<action
android:id="#+id/toNestedGraph"
app:destination="#id/graph_nested">
<argument
android:name="arg_name"
app:argType="string" />
</action>
</fragment>
</navigation>

Related

How to send data with NavigationComponent in Android

In my application I have 2 fragment and for show these I use NavigationComponent and I want sent some data.
My navigation codes:
<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/nav_home"
app:startDestination="#id/recipesFragment">
<fragment
android:id="#+id/recipesFragment"
android:name="myapp.ui.fragments.recipe.RecipesFragment"
android:label="#string/recipes"
tools:layout="#layout/fragment_recipes" >
<action
android:id="#+id/action_recipesFragment_to_menuFragment"
app:destination="#id/menuFragment" />
<argument
android:name="isUpdatedData"
app:argType="boolean"
android:defaultValue="false" />
</fragment>
<dialog
android:id="#+id/menuFragment"
android:name="myapp.ui.fragments.recipe.menu.MenuFragment"
android:label="fragment_menu"
tools:layout="#layout/fragment_menu" >
<action
android:id="#+id/action_menuFragment_to_recipesFragment"
app:destination="#id/recipesFragment" />
</dialog>
</navigation>
I set argument with default value, but when use this action into MenuFragment not access to me for set value!
MenuFragment codes:
submitBtn.setOnClickListener {
val action = MenuFragmentDirections.actionMenuFragmentToRecipesFragment(true)
findNavController().navigate(action)
}
When write true for value of actionMenuFragmentToRecipesFragment show me below error:
Too many arguments for public open fun actionMenuFragmentToRecipesFragment(): MenuFragmentDirections.ActionMenuFragmentToRecipesFragment defined in myapp.ui.fragments.recipe.menu.MenuFragmentDirections
How can I fix it and set data with defaultValue?
Hi I hope this answer be useful for you
if you would like to use default value below code will help you:
findNavController().navigate(MenuFragmentDirections.actionMenuFragmentToRecipesFragment().setIsUpdatedData(true))
when you set default value for your argument compiler recognize code does not need setter and if you want change it you have to call setter method of argument in your navigation for custom fragment

How to reuse a fragment in different navigation graphs with safe args enabled?

I'm trying to reuse a fragment in different navigation graphs with safe args enabled. I noticed that if the actions are different I get a compilation error. This is because the xxxFragmentDirections autogenerated code will only generate one of the actions.
In nav_graph_1.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/dest_one" />
</fragment>
...
In nav_graph_2.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/other_action"
app:destination="#+id/other_dest" />
</fragment>
...
A simple use case: A bank app that has two flows: withdraw and deposit, therefore you could have two nav graphs. You could have an AmountFragment where you could just enter a number and this could be reused to either withdraw or deposit. However, depending on the flow, the actions/destinations could be different.
Then, how would it be possible to reuse this fragment?
Use navigate() with bundle instead of actions in edge cases. Don’t call
findNavController().navigate(FragmentDirections.goToDetailFragment(id))
but instead use
findNavController().navigate(R.id.DetailFragment, bundleOf("id" to 5))
This way you don’t rely on the generated direction but can still use the Navigation and SafeArgs features of the DetailFragment.
https://code.allaboutapps.at/articles/android-jetpack-navigation-pitfalls/#reuse-fragments-in-multiple-navigation-graphs
This can be achieved by providing the same action ID in both the navigation graph.
In nav_graph_1.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/dest_one" />
</fragment>
...
In nav_graph_2.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/other_dest" />
</fragment>
...
In fragment, navigation can be performed like
NavHostFragment.findNavController(<myFragment>).navigate(R.id.next);
It might not be what you ask, but I faced the problem o re-usability of code in a fragment that I needed in different navigation graphs.
The fragment class that needs to be reused should be declared as abstract:
abstract class ReusableFragment(val type: ReusableEnum): Fragment(){
enum class ReusableEnum{Type1, Type2}
// the rest of your logic will use
// 'type' variable in when() blocks to determine
// specific logic for each case
}
class Type1Fragment: ReusableFragment(ReusableEnum.Type1)
class Type2Fragment: ReusableFragment(ReusableEnum.Type2)
This way, Type1Fragment and Type2Fragment are available in NavigationGraph as independent fragments.

Android Navigation library crash after sending data back

I use Android Navigation library for fragment navigation and try pass data back from second fragment to first. Do it with the next code:
findNavController().setGraph(R.navigation.nav_graph, bundle)
findNavController().popBackStack()
It works fine, but when I try to open the same screen after it, the next error occurred:
java.lang.IllegalArgumentException: navigation destination com.leshchenko.test:id/openPurchaseDetailsAction is unknown to this NavController
Thanks.
UPDATE:
My nav_graph.xml
<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/nav_graph"
app:startDestination="#id/purchasesListFragment">
<fragment
android:id="#+id/purchasesListFragment"
android:name="com.leshchenko.finance.presentation.purchases.PurchasesListFragment"
android:label="fragment_purchases_list"
tools:layout="#layout/fragment_purchases_list">
<action
android:id="#+id/openPurchaseDetailsAction"
app:destination="#+id/purchaseDetailsFragment">
<argument
android:name="purchaseId"
android:defaultValue="-1L"
app:argType="long" />
</action>
</fragment>
<fragment
android:id="#+id/purchaseDetailsFragment"
android:name="com.leshchenko.finance.presentation.purchase_details.PurchaseDetailsFragment"
android:label="fragment_purchase_details"
tools:layout="#layout/fragment_purchase_details">
<argument
android:name="purchaseId"
android:defaultValue="-1L"
app:argType="long" />
</fragment>
Details opens via:
findNavController().navigate(PurchasesListFragmentDirections.openPurchaseDetailsAction())
use findNavController().navigateUp() instead of findNavController().popBackStack()
I think you can just pass your data through viewModel and for fragment finish
just use :
findNavController().popBackStack()
eg:
view.setOnClickListener {
// pass Data through ViewModel
// viewModel.sendData(data)
Navigation.findNavController(it).popBackStack()
}

Passing an argument between two fragments each of them in a different Navigation Graph

Using Android Navigation Component. I have two Navigation Graphs (each of them with their own flow), lets say navigation graphs A and B. I navigate successfully from A to B, but I can't manage to pass an argument from the last Fragment included in graph A to the start Fragment that belongs to graph B.
I am able to pass arguments between fragments that belong to the same graph, but the function to set the arguments is not been generated when navigating between to navigation graphs.
I am trying to accomplish that using safeargs.
Here is the navigation graphs code:
Navigation Graph A:
<navigation android:id="#+id/nav_graph_a"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="#id/fragment1">
<fragment
android:id="#+id/fragment1"
android:name="com.mypackage.fragments.Fragment1"
android:label="Fragment1">
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"/>
</fragment>
<fragment
android:id="#+id/fragment2"
android:name="com.mypackage.fragments.Fragment2"
android:label="Fragment2">
<argument
android:name="thisArgumentAlwaysArrive"
android:defaultValue="null"
app:argType="string"/>
<action
android:id="#+id/action_fragment2_to_nav_graph_b"
app:destination="#id/nav_graph_b"/>
</fragment>
<include app:graph="#navigation/nav_graph_b"/>
Navigation Graph B:
<navigation android:id="#+id/nav_graph_b"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="#id/fragment3">
<fragment
android:id="#+id/fragment3"
android:name="com.mypackage.fragments.Fragment3"
android:label="Fragment3">
<argument
android:name="thisArgumentNeverArrive"
app:argType="string"/>
</fragment>
Any idea on how to accomplish this?
As stated in the documentation:
Note: Safe Args do not support cross-module navigation, as there is no direct action to the destination. In the previous example, although a Directions class would be generated for the target destination in settings, you aren't able to access the generated class from the classpath of the list module.
But you can use a bundle instead of nav_args like this:
From
FristFragmentDirections.actionFirstFragmentToSecondFragment(your_parameter).let { action ->
findNavController().navigate(action)
}
To
FristFragmentDirections.actionFirstFragmentToSecondFragment().let { action ->
findNavController().navigate(
action.actionId,
Bundle().apply { putString("parameter_key", your_parameter) }
)
}
Safe Args is the plugin for Gradle that creates the classes ClassArgs for the destinations.
The default controller provides a way to navigate to another NavGraph, in this case there is an implementation of a Navigator called NavGraphNavigator.
This navigator provides you with a navigate(NavGraph destination,Bundle args) function that passes the arguments to the start fragment.
So, the best way I have found to do what you are trying to achieve is to use the generated class to generate a bundle with the needed arguments.
var myBundle = YourFragmentIdInNavGraphArgs.Builder(var args..).toBundle()
and then use the default NavController to navigate to a navGraph destination like this.
view.findNavController().navigate(R.id.your_action_to_nav_graph_id,myBundle)
the implementation of the Navigator will take care of passing the arguments to the start fragment on the destination.
Hope it helps to some one.

Using DialogFragment as one of the Fragment in Android NavController

I have a graph defined using XML and I added a DialogFragment as one of the Fragment in my NavGraph. when I call NavController.navigate with resId and bundle on that nav controller I don't see any Dialog being displayed. Is there any way I can use DialogFragment instead of standard Fragment?
<fragment
android:id="#+id/noLoginDialog"
android:name="com.ram.view.NoLoginDialog"
android:label="NoLoginDialog">
<argument
android:name="argTitle"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argBody"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argButton"
android:defaultValue="null"
app:type="string"/>
</fragment>
and my action is defined as below
<container_fragment
android:id="#+id/homeFragment"
android:name="com.ram.home.HomeFragment"
android:label="HomeFragment">
<action
android:id="#+id/action_homeFragment_to_noAuthAlertDialog"
app:destination="#id/noLoginDialog"/>
</container_fragment>
My other actions with activity and fragments works just fine.
hum, Navigation Library will never open an Fragment as Dialog, it just replace the first (home destination) to other destination in your NavHostFragment.
Google said:
A destination is any place you can navigate to in your app. While
destinations are usually Fragments representing specific screens..
Please provide more information, like your navigation code (java/kotlin).
Take a read on this official Google post to understand more about Navigation:
https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing
Yes you can call up DialogFragment from navigation, you will just have to explicitly tell the navigation that you are going to use dialog
<dialog
android:id="#+id/simCancelFinish"
android:name="mk.telekom.kiosk.ui.dialogs.SimCancelFinish"
android:label="SimCancelFinish" >
<action
android:id="#+id/action_simCancelFinish_to_stornoSimFragment"
app:destination="#id/stornoSimFragment"
app:popUpTo="#id/stornoSimFragment"
app:popUpToInclusive="true" />
</dialog>

Categories

Resources