J'ai un problème avec le nouveau composant Android Architecture de navigation lorsque j'essaie de naviguer de d'un fragment à un autre, j'obtiens cette erreur étrange:
Java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
Chaque autre navigation fonctionne bien, sauf celui-ci.
J'utilise:
findNavContoller()
fonction d'extension de Fragment pour accéder à navControler.
Toute aide serait appréciée.
Dans mon cas, si l'utilisateur clique très rapidement deux fois sur la même vue, ce plantage se produira. Vous devez donc implémenter une sorte de logique pour éviter plusieurs clics rapides ... Ce qui est très ennuyant, mais cela semble être nécessaire.
Vous pouvez en savoir plus sur la prévention de ce problème ici: Android Preventing Double Click On A Button
Edit 19/03/2019 : Juste pour clarifier un peu, cet incident n'est pas exclusivement reproductible en "cliquant deux fois sur la même vue très rapidement" . Alternativement, vous pouvez simplement utiliser deux doigts et cliquer sur deux vues (ou plus) en même temps, chaque vue disposant de sa propre navigation. C'est particulièrement facile à faire lorsque vous avez une liste d'éléments. Les informations ci-dessus sur la prévention des clics multiples gèreront ce cas.
Vérifiez currentDestination
avant d'appeler, une navigation peut être utile.
Par exemple, si vous avez deux destinations de fragment sur le graphe de navigation fragmentA
et fragmentB
, et qu’il n’existe qu’une action de fragmentA
à fragmentB
. appeler navigate(R.id.action_fragmentA_to_fragmentB)
donnera IllegalArgumentException
alors que vous étiez déjà sur fragmentB
. Par conséquent, vous devriez toujours vérifier la currentDestination
avant de naviguer.
if (navController.currentDestination?.id == R.id.fragmentA) {
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
Vous pouvez vérifier l'action demandée dans la destination actuelle du contrôleur de navigation.
fun NavController.navigateSafe(
@IdRes resId: Int,
args: Bundle? = null,
navOptions: NavOptions? = null,
navExtras: Navigator.Extras? = null
) {
val action = currentDestination?.getAction(resId)
if (action != null) navigate(resId, args, navOptions, navExtras)
}
Dans mon cas, j’utilisais un bouton de retour personnalisé pour naviguer vers le haut. J'ai appelé onBackPressed()
au lieu du code suivant
findNavController(R.id.navigation_Host_fragment).navigateUp()
Ceci a provoqué le IllegalArgumentException
. Après l'avoir modifiée pour utiliser la méthode navigateUp()
à la place, je n'ai plus eu d'incident.
Cela peut également arriver si vous avez un fragment A avec un ViewPager de fragments B et que vous essayez de naviguer de B à C
Comme dans le ViewPager, les fragments ne sont pas une destination de A, votre graphique ne saurait pas que vous êtes sur B.
Une solution peut être d’utiliser ADirections dans B pour accéder à C
Ce que j'ai fait pour empêcher le crash est le suivant:
J'ai un BaseFragment, là j'ai ajouté ceci fun
pour m'assurer que le destination
est connu par le currentDestination
:
fun navigate(destination: NavDirections) = with(findNavController()) {
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination) }
}
A noter que j'utilise le plugin SafeArgs .
Dans mon cas, le bogue s’est produit parce que j’avais une action de navigation avec les options Single Top
et Clear Task
activées après un écran de démarrage.
Il semble que vous effaciez la tâche. Une application peut avoir une configuration unique ou une série d’écrans de connexion. Ces écrans conditionnels ne doivent pas être considérés comme la destination de départ de votre application.
https://developer.Android.com/topic/libraries/architecture/navigation/navigation-conditional
TL; DR Enveloppez vos appels navigate
avec try-catch
(méthode simple) ou assurez-vous qu'il n'y aura qu'un seul appel de navigate
dans un court laps de temps. Ce problème ne va probablement pas disparaître. Copiez le plus gros extrait de code dans votre application et essayez-le.
Salut. Sur la base de quelques réponses utiles ci-dessus, je voudrais partager ma solution qui peut être étendue.
Voici le code qui a provoqué ce blocage dans mon application:
@Override
public void onListItemClicked(ListItem item) {
Bundle bundle = new Bundle();
bundle.putParcelable(SomeFragment.LIST_KEY, item);
Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}
Un moyen de reproduire facilement le bogue consiste à appuyer avec plusieurs doigts sur la liste des éléments pour lesquels cliquer sur chaque élément est résolu lors de la navigation vers le nouvel écran (essentiellement les mêmes que ceux notés - deux clics ou plus sur une très courte période). ). J'ai remarqué ça:
navigate
fonctionne toujours bien;navigate
sont résolues dans IllegalArgumentException
.De mon point de vue, cette situation peut apparaître très souvent. La répétition de code étant une mauvaise pratique et il est toujours bon d'avoir un point d'influence, j'ai pensé à la solution suivante:
public class NavigationHandler {
public static void navigate(View view, @IdRes int destination) {
navigate(view, destination, /* args */null);
}
/**
* Performs a navigation to given destination using {@link androidx.navigation.NavController}
* found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
* multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
* The navigation must work as intended.
*
* @param view the view to search from
* @param destination destination id
* @param args arguments to pass to the destination
*/
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
try {
Navigation.findNavController(view).navigate(destination, args);
} catch (IllegalArgumentException e) {
Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
}
}
}
Et donc le code ci-dessus ne change que sur une ligne:
Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
pour ça:
NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);
Il est même devenu un peu plus court. Le code a été testé à l’endroit exact où le crash s’est produit. Je n’ai plus fait l’expérience et utilisera la même solution pour d’autres navigations afin d’éviter davantage la même erreur.
Toutes les pensées sont les bienvenues!
Qu'est-ce qui cause exactement le crash
Rappelez-vous que nous travaillons ici avec le même graphe de navigation, le même contrôleur de navigation et la même pile lorsque nous utilisons la méthode Navigation.findNavController
.
Nous avons toujours le même contrôleur et graphique ici. Lorsque navigate(R.id.my_next_destination)
est appelé graphique et modifications de pile arrière presque instantanément alors que l'interface utilisateur n'est pas encore mise à jour. Juste pas assez vite, mais ça va. Une fois que la pile arrière a été modifiée, le système de navigation reçoit le deuxième appel navigate(R.id.my_next_destination)
. Depuis que la pile arrière a changé, nous fonctionnons maintenant par rapport au fragment supérieur de la pile. Le fragment du haut est celui auquel vous accédez en utilisant R.id.my_next_destination
, mais il ne contient aucune autre destination avec l'ID R.id.my_next_destination
. Ainsi, vous obtenez IllegalArgumentException
à cause de l'ID sur lequel le fragment ignore tout.
Cette erreur exacte peut être trouvée dans NavController.Java
method findDestination
.
Cela m'est arrivé, mon problème était que je cliquais sur un FAB sur tab item fragment
. J'essayais de naviguer d'un fragment d'élément d'onglet vers another fragment
.
Mais selon Ian Lake dans cette réponse nous devons utiliser tablayout
et viewpager
, pas de composant de navigation soutien . Pour cette raison, il n'y a pas de chemin de navigation de tablayout contenant fragment à fragment d'élément de tabulation.
ex:
containing fragment -> tab layout fragment -> tab item fragment -> another fragment
La solution consistait à créer un chemin à partir d'une disposition d'onglet contenant un fragment à un fragment voulu, par exemple: chemin: container fragment -> another fragment
Inconvénient:
J'ai attrapé cette exception après avoir renommé des classes. Par exemple: j'avais des classes appelées FragmentA
avec @+is/fragment_a
dans le graphe de navigation et FragmentB
avec @+id/fragment_b
. Ensuite, j'ai supprimé FragmentA
et renommé FragmentB
en FragmentA
. Ainsi, après que le nœud de FragmentA
soit resté dans le graphe de navigation, et que le nœud de Android:name
du FragmentB
ait été renommé path.to.FragmentA
. J'avais deux nœuds avec le même Android:name
et différents Android:id
, et l'action dont j'avais besoin était définie sur le nœud de la classe supprimée.
Il m’arrive de penser que j’appuie deux fois sur le bouton Retour. Au début, j'intercepte KeyListener
et remplace KeyEvent.KEYCODE_BACK
. J'ai ajouté le code ci-dessous dans la fonction nommée OnResume
pour le fragment, puis cette question/ce problème est résolu.
override fun onResume() {
super.onResume()
view?.isFocusableInTouchMode = true
view?.requestFocus()
view?.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
activity!!.finish()
true
}
false
}
}
Lorsque cela m'arrive une seconde fois et que son statut est identique à celui de la première, je constate que j'utilise peut-être la fonction adsurd
. Analysons ces situations.
Tout d'abord, FragmentA navigue vers FragmentB, puis FragmentB navigue vers FragmentA, puis appuyez sur le bouton Précédent ... le crash apparaît.
Deuxièmement, FragmentA navigue vers FragmentB, puis FragmentB navigue vers FragmentC, FragmentC navigue vers FragmentA, puis appuyez sur le bouton Précédent ... le crash apparaît.
Je pense donc que lorsque vous appuierez sur le bouton Précédent, FragmentA retournera à FragmentB ou FragmentC, ce qui provoquera un désordre de connexion. Enfin, je trouve que la fonction nommée popBackStack
peut être utilisée pour le retour plutôt que pour la navigation.
NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
.popBackStack(
R.id.teacher_prepare_lesson_main_fragment,false
)
Jusqu'ici, le problème est vraiment résolu.