Navigation Component: Passing bundles to destinations via Bottom Navigation - android

In my application I have a host fragment for a group of views that a user can navigate to via a BottomNavigationView. This BottomNavigationView is connected to my nav controller via setupWithNavController.
My host fragment receives a bundle with some information that I would like each fragment to receive as it is navigated to (via the bottom nav view) as a bundle.
My current solution looks like
mutableListOf<NavDestination>().apply {
addIfNotNull(graph.findNode(R.id.frag1))
addIfNotNull(graph.findNode(R.id.frag2))
addIfNotNull(graph.findNode(R.id.frag3))
forEach {
// args is a safe args object for this host fragment
it.addArgument("argName", NavArgument.Builder().setDefaultValue(args.argName).build())
}
}
While this works it will not scale very well as I am manually adding the arguments for each destination. Since I am not manually navigating to each destination, rather it is done by the BottomNavigationView I'm not sure how to manually add this bundle.

navController.addOnDestinationChangedListener { controller, dest, args ->
when (dest.label) {
"YOUR_LABEL_HERE" -> {
val arg01 = NavArgument.Builder().setDefaultValue("SOME VALUE").build()
val arg02 = NavArgument.Builder().setDefaultValue("SOME OTHER VALUE").build()
dest.addArgument("KEY_NAME", arg01)
dest.addArgument("OTHER_KEY_NAME", arg02)
}
}
}
try this. It should work fine.

Related

Difference between Navigating one fragment to another using fragment id and action id using nav graph in android jetpack library

I am using jetpack navigation graph in my android project. Normally to navigate one to another fragment we connect a link between them in nav graph and call like this
findNavController().navigate(R.id.action_navigation_login_to_navigation_send_code)
But We can call the fragment this way as well without creating the link.
findNavController().navigate(R.id.navigation_send_code)
What is the basic difference? In my sense, both do the same work.
Both versions back to the same navController navigate() function that takes in an integer resource id whether it's a fragment id or an action id.
Navigate to a destination from the current navigation graph. This supports both navigating
via an {#link NavDestination#getAction(int) action} and directly navigating to a destination.
Internally it calls this navigate() version where it examines whether it's an action id or not.
If it's an action id, then it gets the destination id from it; if not an action id it consider it as a destination id and continue on; notice the comments in below:
#IdRes int destId = resId;
final NavAction navAction = currentNode.getAction(resId);
Bundle combinedArgs = null;
if (navAction != null) { // here the destId is an action id
if (navOptions == null) {
navOptions = navAction.getNavOptions();
}
destId = navAction.getDestinationId(); // resets the destId to the destination id
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
combinedArgs = new Bundle();
combinedArgs.putAll(navActionArgs);
}
}
// Now destId is the destination fragment id.. continue on...
So, technically no difference, but adding an action id adds an extra step of getting the fragment id which is nothing in terms of scalable apps.
The other difference, that if you put an actionId instead of a fragment id, you'd have an extra feature of navOptions that can be returned from the navGraph like adding enter/exit animation.

How to navigate to a fragment if current fragment is unknown?

I have some non-optimal app architecture and need to navigate from one fragment to another but it's not clear what is the current destination and arriving.
The simple way is to use something like this:
if (findNavController().currentDestination?.id == R.id.fragmentA)
findNavController().navigate (R.id.action_fragmentA_to_fragmentB)
but how can I make a navigation path dynamically if I know only fragmentB's name / id? Something like
fun navigate(arriveFragment) =
findNavController().navigate (R.id.action_$currentFragment_to_$arriveFragment)
you can navigate using the fragment id
findNavController().navigate(R.id.fragmentB)
navigate() also accept navArguments and navOptions
val args = bundleOf(
"key" to "value"
)
val options = navOptions {
popUpTo(R.id.fragmentA)
}
findNavController.navigate(R.id.fragmentB, args, options)

Navigate between fragments using button in toolbar

In my app, I want the process of creating a new record to be divided into several steps in which users are queried about different information. So I created an activity with NavHostFragment and want to use a navigation graph to switch between fragments using a next button in the toolbar of this activity.
Is it possible to configure the button to navigate between fragments based on a navigation graph? Is this a good approach? Should I rather use a new activity for each step? I am new to android development so I am not sure what is the best way to do this.
You can handle it with your navigation graph
Handle toolbar click events in your fragments:
https://stackoverflow.com/a/30077965/11982611
While handling each fragment's next button click event implement your navigation code
findNavController().navigate(Your action)
Handle all navigation process in your Activity's OnItemOpotionsSelected Listener
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navHostFragment = supportFragmentManager.primaryNavigationFragment
val fragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
when(item.itemId)
{
android.R.id.home->super.onBackPressed()
R.id.next-> {
if(fragment is Fragment1)
{
fragment.findNavController().navigate(Fragment1toFragmen2 action)}
if(fragment is Fragment2)
{
fragment.findNavController().navigate(Fragment2toFragmen3 action)}
}
return true
}

Pass to fragment using Navigation from Activity

I have an activity with two fragments. Either fragment is located on container in activity. Also activity has toolbar with settings button. I should pass on settings fragment using Navigation by click on settings button, but i have problems with it, because findNavController(R.id.home_nav).navigate(R.id.action_second_fragment_to_settings_fragment)
works if we pass to settings screen and second fragment is active, but from first active will be crash.
What do i need to do for solving this problem? I used just actions in Navigation component for passing between fragment. May be there is useful solution for this task
if your navController in MainActivity, with this code you can access to navController and send data to fragment.
load new fragment :
val bundle = Bundle()
bundle.putString("id", id)
(activity as MainActivity).navController.navigate(R.id.orderDetailFragment, bundle)
for get data in onViewCreated in fragment :
arguments?.let {
if (it.getSerializable("id") != null) {
val id = it.getString("id")
}
}
i hope it's useful

Navigation component with bottom navigation

I am working with navigation component and bottom navigation
val navController = indNavController(R.id.nav_host_fragment)
bottom_navigation.inflateMenu(R.menu.bottom_navigation_menu)
bottom_navigation.setupWithNavController(navController)
and I am facing the next issue:
When an item is selected in the bottom navigation, then a fragment is loaded. The problem comes when I press again the same item, then a new fragment will be loaded, which it does not make sense at all.
Example:
1- User selects menu item A, then FragmentA is loaded.
2- User selects again menu item A, then a new FragmentA will be loaded,
I was trying to use
bottom_navigation.setOnNavigationItemSelectedListener { }
But then the bottom navigation will not work with the navController.
So the question is: there is a way to handle this situation in order to load again a new fragment when the user is in that screen already?
Finally, I was able to fix this issue.
As I said before, this code:
bottom_navigation.setupWithNavController(navController)
is using
bottom_navigation.setOnNavigationItemSelectedListener { }
so every time I select / reselect an item, the navController will load a new fragment. I checked the javadoc for setOnNavigationItemSelectedListener() and it says:
Set a listener that will be notified when a bottom navigation item is selected. This listener * will also be notified when the
currently selected item is reselected, unless an {#link *
OnNavigationItemReselectedListener} has also been set.
So what I did is to add the next line to my code:
bottom_navigation.setOnNavigationItemReselectedListener { }
and that's all. The navController will load a fragment when an item is selected but not when an item is reselected in the bottom navigation.
I prefer to use the listener from navController:
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}
So the listener is triggered when the destination changes - not by clicking bottom_navigation.
Because setOnNavigationItemSelectedListener is already used when setupWithNavController is declared.
Try this to ignore the user's click on the same selected item:
bottom_navigation.apply {
setOnNavigationItemSelectedListener {
if (it.itemId == bottom_navigation.selectedItemId) {
true
} else when (it.itemId) { ... }
when you use bottom_navigation.setOnNavigationItemSelectedListener { } before bottom_navigation.setupWithNavController(navController) the OnNavigationItemSelectedListener is overrided inside setupWithNavController function. So use
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}

Categories

Resources