Avoiding Android navigation IllegalArgumentException in NavController [duplicate] - android

This question already has answers here:
IllegalArgumentException: navigation destination xxx is unknown to this NavController
(40 answers)
Closed 2 years ago.
I recently switched over to Android Navigation, but have encountered a fair amount of situations (in different parts of the code), where I get:
Fatal Exception: java.lang.IllegalArgumentException
navigation destination com.xxx.yyy:id/action_aFragment_to_bFragment is unknown to this NavController
In every case, the code are simple calls like:
findNavController(this, R.id.navigation_host_fragment).navigate(R.id.action_aFragment_to_bFragment)
Usually in response to a button press.
It's unclear why this error is getting thrown. My current suspicion is that the onClickListener is somehow getting called twice on some devices, causing the navigate to be called a second time (causing it to be in the wrong state at the time). The reason for this suspicion is that this most often seems to happen in cases where one might have a "long" running operation before the navigate call. I can't recreate this on my own device, though.
Ideas on how to avoid this issue (or indeed, what the actual root cause of the issue is)?
I don't want to use Global Actions; I'm wary of introducing even more unexpected states to the backstack. And I'd really prefer not to have to try and test for the currentstate every time a navigate call is made.

You can use the below code before navigating to check whether the current destination is correct or not. This will make sure that the call happens from the current fragment only. This issue can be reproduced by clicking on two views simultaneously(Two items in a list).
if (findNavController().currentDestination?.id == R.id.currentFragment) {
findNavController().navigate(R.id.action_current_next)
}

Okay, let me explain you this exception was pass because we are calling an action from a fragment(destination) which is not the current destination on the stack.
i.e
you're calling an action
R.id.action_aFragment_to_bFragment
from fragmentA but in navController current destination is other that fragmentA. That's why navController through that exception:
navigation destination com.xxx.yyy:id/action_aFragment_to_bFragment is unknown to this NavController
you can check the current destination before navigate. like
Toast.makeText(context,view?.findNavController()?.currentDestination?.label,Toast.LENGTH_SHORT).show()
That will show you the current destination and i'm sure that's will be some other destination.
This will happens when we replace a fragment other than actions(like via old methods not with navigation) or we popUp that fragment before calling an action.
If that will be the case then you have to use Global Action because they don't care what current destination is.

Related

How to avoid "navigation XYZ cannot be found from the current destination"

Some crashes have popped out on my Firebase's crashlytics with the exception
Fatal Exception: java.lang.IllegalArgumentException Navigation ACTION_XYZ cannot be found from the current destination
I've debugged this case and found out the problem:
I have a button which navigates from Fragment A to Fragment B and it works good.
But when you quickly click the button two times - first it navigates correctly, then tries to navigate again, thus the exception.
How should one avoid such a bug? I could just silently catch the exception from the button's click but that looks like a code smell to me.
I also could disable the button after the first click, but I'm wondering if there is a more elegant and cleaner way to avoid double navigation to FragmentB?
Thanks for all the answers, cheers!
Before navigating to any screen, you can check whether we have action to that particular destination from the current screen. Something like below. You can use this extension function to navigate safely to any destination.
fun NavController.navigateSafe(directions: NavDirections, navOptions: NavOptions? = null) {
currentDestination?.getAction(directions.actionId)?.let {
navigate(directions, navOptions)
}
}

android intent filter with single activity architecture

im currently working on an app, and i want to be able to share images to my app.
with multiple activities this doesn't seem to be a problem, since you define your activity as intent receiver.
however how would i do this, with the single activity architecture google is pushing with its navigation api ?
should the single main activity handle it or does it make sense to create a second activity in this scenario.
i couldn't really find anything on that topic in the android dev docs
Pass Uri to your navController instead of navigating manually by fragment id.
val deeplink = Uri.parse("android-app://androidx.navigation.app/yourFragmentId")
findNavController().navigate(deeplink)
Unlike navigation using action or destination IDs, you can navigate to any URI in your graph, regardless of whether the destination is visible. You can navigate to a destination on the current graph or a destination on a completely different graph.
When navigating using URI, the back stack is not reset. This is unlike other deep link navigation, where the back stack is replaced when navigating. popUpTo and popUpToInclusive, however, still remove destinations from the back stack just as though you had navigated using an ID.

How to make it so that after naving to a fragment, the last fragment can no longer be navigated to with the back button?

I have a nav structure like so:
StaringFragment -> nestedNavGraph
|
HomeFragment -> SecondFragment
I need to make it so that after the user navigates away from StartingFragment to the nestedNavGraph the user can no longer hit the back button to return to the StartingFragment.
I am basically performing a permissions check, and if the check succeeds I navigate from the StartingFragment to the nestedNavGraph with this code:
findNavController(R.id.nav_host_frag).navigate(R.id.nestedNavGraph)
Then, on the action arrow between the StartingFrag -> nestedNavGraph in the nav graph editor, I set popUpTo to HomeFragment and inclusive to true. Did I misunderstand the documentation?
What's the proper way to remove the StartingFragment from the backstack as soon as I navigate away from it?
As per the documentation, you set app:popUpTo to the destination you want to pop - you don't want to pop the HomeFragment, you want to pop the StartingFragment, so you should set your app:popUpTo to the ID of the StartingFragment.
Note that as per the Principles of Navigation, you should always have a fixed starting destination - you shouldn't be using the start destination for conditional navigation - many of the APIs in Navigation, such as NavigationUI, assume you're correctly following the Principles of Navigation and that the start destination of your graph remains on your back stack.
Keep in mind that Android will restore your state (including what fragment you were at) when returning to your activity (say, after the user manually revoked the permissions you asked for) - your Fragment itself (or the activity if the permission really is globally required on every single screen in your whole app) should always be checking for permission - you can't rely on your StartingFragment always being called.
I haven't used the nav lib yet, but from reading this bit in the documentation maybe you just have a typo and need to replace popTo with popUpTo?
To pop destinations when navigating from one destination to another, add a app:popUpTo attribute to the associated element. app:popUpTo tells the Navigation library to pop some destinations off of the back stack as part of the call to navigate(). The attribute value is the ID of the most recent destination that should remain on the stack.
You can also include app:popUpToInclusive="true" to indicate that the destination specified in app:popUpTo should also be removed from the back stack.

Android navigate up the right graph when using NavController

I'm currently using Android Navigation Architecture in my project. It has a feature that can launch any fragment with a shortcut.
Suppose my navigation graph looks like this: A->B->C->D.
When I'm at fragment A , I directly navigate to fragment D with NavController.navigate(R.id.fragment_d). But when I'm press back button, it returns to fragment A. Is there any way to let the destination navigate back to its parent in navigation graph? (I mean, in this case, D back to C, B then back to A).
Thanks in advance.
Generally, you should always avoid creating a synthetic back stack when within your own app (and only do this when launching your app from a deep link outside your app, i.e., via an app shortcut).
However, you can approach this in one of two ways:
If you're okay with resetting the state on A, you can use NavDeepLinkBuilder to construct a set of Intents suitable for restarting your task on the chosen destination:
navController.createDeepLink()
.setDestination(R.id.destination_d)
.createTaskStackBuilder()
.startActivites()
Just call navigate() multiple times.
navController.navigate(R.id.destination_b)
navController.navigate(R.id.destination_c)
navController.navigate(R.id.destination_d)
I think you should be able to use navControler.navigateUp() on the destination if you have correctly introduced the parent fragment for your fragments. Also, consider the difference between the device back button and up back-arrow to go up to the parent

Android architecture navigation popBackStack throws IllegalArgumentException "back stack is empty"

I have an app that's using the new navigation library. I've attached a listener to the NavController and here's what it looks like as I navigate through the app:
main_fragment
authentication_subgraph
loading_fragment
set_username_fragment calls popBackStack(R.id.authentication, true)
loading_fragment removed as a part of the pop back stack above.
main_fragment
squad_fragment
In some cases reported to me through Crashlytics I see that a call from squad_fragment throws an IllegalArgumentException, the call looks like:
close.setOnClickListener {
if (isResumed) {
findNavController().popBackStack()
}
}
This should and in every test I've run simply pop squad_fragment and leave me with main_fragment, but in some cases (20 times last night) I got an exception here saying that the back stack is empty. How can this be? Anything I'm doing wrong?
UPDATE
I've been able to replicate the issue finally. This appear to happen when I navigate anywhere from main_fragment then background the app, do a bunch of other things, then return to the app from the recents list. It would seem that the navigation library doesn't restore the back stack? I'm using version 1.0.0-alpha02 right now. It appears that if instead of calling popBackStack() I call navigateUp() it works, but this concerns me because the way I exit my authentication subgraph currently is by calling popBackStack(R.id.authentication, true) in order to return to whatever fragment triggered the auth flow. This will eventually break in the same manner.
Looking at the code in NavController it appears that the back stack is saved and restored. So I don't understand why the backstack comes back as empty. Additionally, NavHostFragment appears to be backing up the NavController state as well as recovering it so I don't know why my backstack is empty.

Categories

Resources