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)
Related
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.
I have this scenario in which the user clicks on a button in composable A then selects an item from the list from composable B and selects another item from the list from composable C.
My problem is when I select an item from screen C I want to navigate back to screen A with whatever I selected in B & C. But popBackStack doesn't work when arguments are given.
Here is the code,
navController.popBackStack(route = Screen.SelectPlan.route + "?regionId=${region.id}&operatorId=${operator.id}")
Right now, I see that popBackStack does take a route argument, but converts it to a hashcode to navigate back instead of creating a Uri-like navigate function.
I encountered the same issue and have just discovered the solution for myself.
When you navigate to a destination, using args like so:
val arg1 = "someValue"
val arg2 = "someOtherValue"
navController.navigate("Destination/$arg1/$arg2")
... this route is stored in the newly created backstack entry, not with the values of those args, but with their names as assigned in your NavHost.
Assume my NavHost contains a composable with a route of Destination/{arg1}/{arg2}.
If the .navigate() call in my previous example is executed, an entry will be added to the backstack with this route Destination/{arg1}/{arg2}, not this route Destination/someValue/someOtherValue.
You didn't provide your NavHost in the post, but if you replace the values of those args in the call to .popBackStack() with their names you assign in your NavHost, it should work for you.
Try put "/" instead of ?
navController.popBackStack(route = Screen.SelectPlan.route + "/regionId=${region.id}&operatorId=${operator.id}")
This is indeed a bug and it bothered me for a long time. After a long time of research and reading the navigation source code, I wrote my own extension function NavController.popBackStack to fix this problem. It is tricky but works for me. Also note that this version of extension function does not support the inclusive and saveState options (due to the current navigation API limitation), so you can't restoreState when you return to the current destination.
import android.content.Intent
import androidx.navigation.NavController
fun NavController.popBackStack(route: String): Boolean {
if (backQueue.isEmpty()) {
return false
}
var found = false
var popCount = 0
val iterator = backQueue.reversed().iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
popCount++
val intent = entry
.arguments
?.get("android-support-nav:controller:deepLinkIntent") as Intent?
if (intent?.data?.toString() == "android-app://androidx.navigation/$route") {
found = true
break
}
}
if (found) {
navigate(route) {
while (popCount-- > 0) {
popBackStack()
}
}
}
return found
}
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
I'm trying to find if a fragment is already in the backStack of my NavHostFragment (so it automatically manages the fragment transactions and backstack), in order to pop back to it when the user selects that destination from my Side Menu, instead of adding another new fragment to the backstack.
Here's the catch: Many of my fragments are the same Class, let's call it ArticleListFragment, and their contentId param (a simple string id) changes what is being displayed in those fragments.
This means I cannot use nav_host_fragment.childFragmentManager.findFragmentById() since multiple of my fragments in the backstack have the same fragment id.
What I've tried so far is this
var foundIndex = -1
for (i in 0 until nav_host_fragment.childFragmentManager.backStackEntryCount) {
val currFragmentTag = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).name
val currFragmentId = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).id
//val currFragment = nav_host_fragment.childFragmentManager.findFragmentByTag(currFragmentTag) // always returns null
val currFragment = nav_host_fragment.childFragmentManager.findFragmentById(currFragmentId) // always returns null
// currentFragment is null so the check always fails
if (currFragment is ArticleListFragment && currFragment.contentId == "a value I need to check") {
foundIndex = i
break
}
}
Am I doing something wrong? Is there another way to check if a fragment, added by the Android Navigation Component, is already in the back stack?
Well, I had the same issue, i can not say that this is a perfect solution but you can get it with
findNavController()
.backStack
.firstOrNull { it.destination.id == ID_YOU_WANT_TO_CHECK }
backstack is actually restricted so the method needs to be Suppressed.
#SuppressLint("RestrictedApi")
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.