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)
}
}
Related
I have written the code below, to try to, first clear everything and then navigate back to the very first screen. When I tap the button, it first goes back to the previous screen then when I go forward and tap again it goes to the Main screen.
rootNavHostController?.navigate("Main"){
popUpTo(rootNavHostController.graph.findStartDestination().id){
inclusive = true
}
}
Why is this strange behavior. And what can I do to resolve it?
EDIT: Even the code below gives the same behavior, it is like, it first in the stack goes one step back and then afterwards navigates to the correct screen. How can I clear everything and navigate directly to the Main screen?
rootNavHostController?.navigate(Graph.ENTRY){
popUpTo(rootNavHostController?.currentBackStackEntry?.destination?.route.toString()){
inclusive = true
}
}
I'm trying to implement this scenario. I have a sign in screen with a sign in button. When the user clicks the button and gets authenticated, I send the user to the profile screen. The problem comes when the user hits the back button. Instead of existing the app, it goes back to the sign in screen, which is bad. If I have had activities, I have called finish() in the sign in activity when going forward to profile activity, and when the user pressed back, it quits the app. How to do the same thing using navigation?
You need to clear the navigation stack. You can do it in many ways.
For example, if you know that you only have a single item in your back stack, you can use popBackStack:
navController.popBackStack()
navController.navigate(Destinations.Profile)
Or you can use popUpTo and specify the first destination you wanna pop up, and add inclusive parameter:
navController.navigate(Destinations.Profile) {
popUpTo(Destinations.Login) {
inclusive = true
}
}
For creating viewModel instance every time when composable is started you need do this:
val viewModel= remember { MyViewModel() }
That's it. No need in popBackStack and e.t.c.
But i recommend you to found way to listen changes of data in your viewModel.
That will be more clear. JetPack Compose gives you another way how to write code and make Android application, so use it)
In Navigation Component, how can one detect if fragment is brought to front from a pop event?
I go from A to B, now I close B using back key, it returns to A, now in A (in onViewCreated event) I want to detect it's coming from B.
If we're using a single NavController.
findNavController((R.id.nav_host_fragment)
.addOnDestinationChangedListener{ hostController, destination, _ ->
val push = currentBackStackSize < hostController.bacStack.size // else pop
// Then save current backstack size
}
This solution might not be always correct, but currently I can't think of an edge case. Please feel free to correct me.
Here is my solution.
In A, add a navigation argument with default value false (in the nav_graph.xml)
In B, add a navigation back to A. To handle back button pressed, add the following in onCreate()
requireActivity().onBackPressedDispatcher.addCallback {
val action = BDirections.actionBFragmentAFragment(true)
findNavController().navigate(action)
}
Now you can determine how A appears. Also, use popUpTo to handle circular logic properly. Let me know if you see any flaws in this approach.
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.
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.