Navigation component Kotlin - cannot be found from the current destination
Here is Java version of NavigationUtils class for safe navigation:
public abstract class NavigationUtils {
/**
* This function will check navigation safety before starting navigation using direction
*
* @param navController NavController instance
* @param direction navigation operation
*/
public static void navigateSafe(NavController navController, NavDirections direction) {
NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination != null) {
NavAction navAction = currentDestination.getAction(direction.getActionId());
if (navAction != null) {
int destinationId = orEmpty(navAction.getDestinationId());
NavGraph currentNode;
if (currentDestination instanceof NavGraph)
currentNode = (NavGraph) currentDestination;
else
currentNode = currentDestination.getParent();
if (destinationId != 0 && currentNode != null && currentNode.findNode(destinationId) != null) {
navController.navigate(direction);
}
}
}
}
/**
* This function will check navigation safety before starting navigation using resId and args bundle
*
* @param navController NavController instance
* @param resId destination resource id
* @param args bundle args
*/
public static void navigateSafe(NavController navController, @IdRes int resId, Bundle args) {
NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination != null) {
NavAction navAction = currentDestination.getAction(resId);
if (navAction != null) {
int destinationId = orEmpty(navAction.getDestinationId());
NavGraph currentNode;
if (currentDestination instanceof NavGraph)
currentNode = (NavGraph) currentDestination;
else
currentNode = currentDestination.getParent();
if (destinationId != 0 && currentNode != null && currentNode.findNode(destinationId) != null) {
navController.navigate(resId, args);
}
}
}
}
private static int orEmpty(Integer value) {
return value == null ? 0 : value;
}
}
You can use this class like this:
NavController navController = Navigation.findNavController(view);
NavigationUtils.navigateSafe(navController, R.id.action_firstFragment_to_secondFragment, null);
or:
NavController navController = Navigation.findNavController(view);
NavDirections direction = FirstFragmentDirections.actionFirstFragmentToSecondFragment(yourModel, bundleArgs);
NavigationUtils.navigateSafe(navController, direction);
This is more a heads up than an answer. But I hope it helps.
Summary: (As others have already said:) Successive calls to Navigation functions are the reason of most of these exceptions.
Given how the android components are structured, specially how MediatorLiveData works, people may sometimes want to join data nodes in a single observable data holder (LiveData).
If an observation of this mediator is linked to dynamic Navigation functions, bugs will undoubtedly appear.
The reason is that sources may change a LiveData value a successive number of times equal to the amount of sources connected to the Mediator.
This is a perfectly good idea, BUT. Repeated changes to the NavController will definitely result in undesirable outcomes.
This may include:
popping the backStack twice.
Going from A -> B twice in a row giving an exception of A not found
the second time.
This is a big testing problem specially since the issue of one Fragment may cascade to the underlaying stacks, and so when an exception of Direction not found may arise in one Fragment, The real culprit may be found on the Fragment on TOP of the one giving the exception.
In reality this would be easily solved by creating a self cancelling thread executor scheduled.cancel(true);
with a delaying tolerance on the mediatorLiveData itself (the onChange to be precise, not the setValue() since eager inner state updates are the entire and only purpose/joke of the mediator IMHO (sorry, no postValue() allowed!)).
Not to mention that the mediator itself is an incomplete component...
Another easier approach is to make sure that onChange calls from a MutableLiveData are performed if and only if !Object::Equals, and prevent repeating calls to onChange() which is still a testament of the incompleteness of the MediatorLiveData/LiveData. (Just be extremely careful with Lists)
At all costs avoid performing successive calls to a NavController, and if you somehow MUST, then a delayed runnable may be your only way to achieve it.
Firstly you should not pass requireView()
when trying to retrieve your Nav controller - navController = Navigation.findNavController(requireView())
. You should be passing the actual Navigation Host Fragment instance.
Secondly the issue is being caused because you are trying to call a Navigation path from B -> C, when on fragment A.
Your direction path is from B -> C
val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
But you navigate up first so you are actually now on Fragment A when trying to execute the navigation:
navController?.navigateUp()
navController?.navigate(action)
I have created an extension function to check the feasibility of starting an action from the current destination.
fun NavController.navigateSafe(@IdRes resId: Int, args: Bundle? = null) {
val destinationId = currentDestination?.getAction(resId)?.destinationId.orEmpty()
currentDestination?.let { node ->
val currentNode = when (node) {
is NavGraph -> node
else -> node.parent
}
if (destinationId != 0) {
currentNode?.findNode(destinationId)?.let { navigate(resId, args) }
}
}}
And the orEmpty()
part is extension over Int?
as follows:
fun Int?.orEmpty(default: Int = 0): Int {
return this ?: default
}