IllegalArgumentException: la destination de navigation xxx est inconnue de ce NavController


139

J'ai un problème avec le nouveau composant Architecture de navigation Android lorsque j'essaie de naviguer d' un fragment à un autre , j'obtiens cette erreur étrange:

java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController

Toute autre navigation fonctionne bien sauf celle-ci.

J'utilise la findNavController()fonction de Fragment pour accéder au fichier NavController.

Toute aide serait appréciée.


Veuillez fournir du code pour une meilleure compréhension.
Alex

12
Cela m'arrive aussi.
Eury Pérez Beltré

Jusqu'à présent, le taux d'occurrence de ce bogue a été réduit avec les nouvelles versions de la bibliothèque, mais je pense que la bibliothèque n'est pas encore bien documentée.
Jerry Oka pour le

Réponses:


76

Dans mon cas, si l'utilisateur clique deux fois très rapidement sur la même vue, ce plantage se produira. Il faut donc implémenter une sorte de logique pour éviter plusieurs clics rapides ... Ce qui est très ennuyeux, mais cela semble nécessaire.

Vous pouvez en savoir plus sur la prévention de cela ici: Android Empêcher le double-clic sur un bouton

Edit 19/03/2019 : Juste pour clarifier un peu plus, ce crash n'est pas exclusivement reproductible en "cliquant sur la même vue deux fois très très rapidement". Alternativement, vous pouvez simplement utiliser deux doigts et cliquer sur deux (ou plus) vues en même temps, où chaque vue a sa propre navigation qu'elle effectuerait. 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 traiteront ce cas.

Edit 16/04/2020 : Juste au cas où vous ne seriez pas très intéressé par la lecture de ce post de Stack Overflow ci-dessus, j'inclus ma propre solution (Kotlin) que j'utilise depuis longtemps maintenant.

OnSingleClickListener.kt

class OnSingleClickListener : View.OnClickListener {

    private val onClickListener: View.OnClickListener

    constructor(listener: View.OnClickListener) {
        onClickListener = listener
    }

    constructor(listener: (View) -> Unit) {
        onClickListener = View.OnClickListener { listener.invoke(it) }
    }

    override fun onClick(v: View) {
        val currentTimeMillis = System.currentTimeMillis()

        if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
            previousClickTimeMillis = currentTimeMillis
            onClickListener.onClick(v)
        }
    }

    companion object {
        // Tweak this value as you see fit. In my personal testing this
        // seems to be good, but you may want to try on some different
        // devices and make sure you can't produce any crashes.
        private const val DELAY_MILLIS = 200L

        private var previousClickTimeMillis = 0L
    }

}

ViewExt.kt

fun View.setOnSingleClickListener(l: View.OnClickListener) {
    setOnClickListener(OnSingleClickListener(l))
}

fun View.setOnSingleClickListener(l: (View) -> Unit) {
    setOnClickListener(OnSingleClickListener(l))
}

AccueilFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}

23
La modification sur l'utilisation de 2 doigts et le clic sur 2 vues en même temps! C'est la clé pour moi et cela m'a aidé à reproduire le problème facilement. Grande mise à jour avec ces informations.
Richard Le Mesurier

Pendant la phase de débogage, j'ai cliqué alors que l'application était bloquée en attendant de poursuivre l'exécution. On dirait un autre cas de deux clics consécutifs sur l'IDE
Marco

1
Merci pour cela.
M'a

58

Vérifiez currentDestinationavant d'appeler naviguer peut être utile.

Par exemple, si vous avez deux destinations de fragment sur le graphique de navigation fragmentAet fragmentB, et qu'il n'y a qu'une seule action de fragmentAà fragmentB. appel navigate(R.id.action_fragmentA_to_fragmentB)aura pour résultat IllegalArgumentExceptionlorsque vous étiez déjà sur fragmentB. Par conséquent, vous devez toujours vérifier le currentDestinationavant de naviguer.

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}

3
J'ai une application de recherche qui navigue avec une action avec des arguments. Ainsi, il pourrait naviguer de la destination actuelle vers lui-même. J'ai fini par faire la même chose sauf navController.currentDestination == navController.graph.node. C'était un peu sale et j'ai l'impression que je ne devrais pas avoir à faire ça.
Shawn Maybush

85
La bibliothèque ne devrait pas nous obliger à faire ce contrôle, c'est en effet ridicule.
DaniloDeQueiroz

J'ai eu le même problème. J'avais un EditText et un bouton «enregistrer» pour stocker le contenu du EditText dans la base de données. Il plantait toujours en appuyant sur le bouton «enregistrer». Je soupçonne que la raison est liée au fait que, pour pouvoir appuyer sur le bouton «enregistrer», je dois me débarrasser du clavier à l'écran en appuyant sur le bouton de retour.
The Fox

Cela vérifie une condition d'erreur, mais cela ne résout pas le problème. Fait intéressant, cette condition est vraie si la backstack de navigation devient vide pour des raisons indésirables.
Mike76

1
même sous iOS, bien que parfois plusieurs ViewController soient poussés lorsque vous appuyez plusieurs fois sur le bouton. Je suppose que Android et iOS ont ce problème.
coolcool1994

47

Vous pouvez vérifier l'action demandée dans la destination actuelle du contrôleur de navigation.

UPDATE a ajouté l'utilisation des actions globales pour une navigation sûre.

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}

1
Cette solution ne fonctionnera pas pour les actions définies en dehors de la currentDestinationliste d'actions de. Supposons que vous ayez défini une action globale et que vous utilisiez cette action pour naviguer. Cela échouera car l'action n'est pas définie dans la liste <action> de currentDestination. L'ajout d'un chèque comme celui-ci currentDestination?.getAction(resId) != null || currentDestination?.id != resIddevrait le résoudre, mais peut également ne pas couvrir tous les cas.
wchristiansen

@wchristiansen, merci pour les notes. J'ai mis à jour le code avec l'utilisation des actions globales
Alex Nuts

@AlexNuts excellente réponse. Je pense que vous pouvez supprimer ?: graph.getAction(resId)-> currentDestination?.getAction(resId)renverra une action pour les actions globales ou non globales (je l'ai testé). Aussi, serait mieux si vous UTILISE Safe args -> plutôt passer navDirections: NavDirectionsque resIdet argsséparément.
Wess

@AlexNuts Notez que cette solution ne prend pas en charge la navigation vers la même destination que la destination actuelle. Iow la navigation de la destination X avec le Bundle Y à la destination X avec le Bundle Z n'est pas possible.
Wess

18

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 en B pour accéder à C


Dans ce cas, le crash ne se produit pas à chaque fois mais n'arrive que rarement. Comment le résoudre?
Srikar Reddy

Vous pouvez ajouter une action globale dans le navGraph et l'utiliser pour naviguer
Abraham Mathew

1
Comme B ne devrait pas avoir besoin de connaître son parent exact, il serait préférable d'utiliser ADirections via une interface comme (parentFragment as? XActionListener)?.Xaction()et notez que vous pouvez conserver cette fonction en tant que variable locale si cela est utile
hmac

pouvez-vous s'il vous plaît partager un exemple de code pour illustrer cela car j'ai le même problème
Ikhiloya Imokhai

n'importe qui pourrait plz un exemple de code, je suis coincé au même problème. Avoir un fragment puis un tabfragment
Usman Zafer

13

Ce que j'ai fait pour éviter le crash est le suivant:

J'ai un BaseFragment, là-dedans, j'ai ajouté ceci funpour m'assurer que le destinationest connu par le currentDestination:

fun navigate(destination: NavDirections) = with(findNavController()) {
    currentDestination?.getAction(destination.actionId)
        ?.let { navigate(destination) }
}

À noter que j'utilise le plugin SafeArgs .


12

Dans mon cas, j'utilisais un bouton de retour personnalisé pour naviguer vers le haut. J'ai appelé onBackPressed()à la place du code suivant

findNavController(R.id.navigation_host_fragment).navigateUp()

Cela a provoqué le IllegalArgumentException. Après l'avoir changé pour utiliser la navigateUp()méthode à la place, je n'ai plus eu de plantage.


Je ne comprends pas la différence entre onBackPressed et celui-ci, toujours coincé avec le bouton de retour du système et le remplacer et le remplacer par cela semble fou
Daniel Wilson

2
Je suis d'accord que cela semble fou. Beaucoup de choses que j'ai rencontrées dans le composant d'architecture de navigation Android semblent un peu folles, il est configuré de manière trop rigide IMO. Penser à faire ma propre implémentation pour notre projet car cela crée juste trop de maux de tête
Neil

Cela ne fonctionne pas pour moi ... Toujours obtenir la même erreur.
Otziii

5

TL; DR Enveloppez vos navigateappels avec try-catch(manière simple), ou assurez-vous qu'il n'y aura qu'un seul appel navigatedans un court laps de temps. Ce problème ne disparaîtra probablement pas. Copiez un extrait de code plus volumineux dans votre application et essayez.

Bonjour. 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 plantage 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 où le clic sur chaque élément se résout dans la navigation vers le nouvel écran (fondamentalement le même que celui noté par les gens - deux clics ou plus dans un laps de temps très court. ). J'ai remarqué ça:

  1. La première navigateinvocation fonctionne toujours bien;
  2. La seconde et toutes les autres invocations de la navigateméthode se résolvent en IllegalArgumentException.

De mon point de vue, cette situation peut apparaître très souvent. Comme la répétition du code est une mauvaise pratique et qu'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 dans une ligne de ceci:

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ù l'accident s'est produit. Je n'en 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 qu'ici, nous travaillons avec le même graphique de navigation, contrôleur de navigation et back-stack lorsque nous utilisons la méthode Navigation.findNavController.

Nous obtenons toujours le même contrôleur et graphique ici. Quand navigate(R.id.my_next_destination)est appelé graphique et back-stack change presque instantanément alors que l'interface utilisateur n'est pas encore mise à jour. Pas assez vite, mais ça va. Une fois que la pile arrière a changé, le système de navigation reçoit le deuxième navigate(R.id.my_next_destination)appel. Depuis que le back-stack a changé, nous opérons maintenant par rapport au fragment supérieur de la pile. Le fragment supérieur est le fragment vers R.id.my_next_destinationlequel vous naviguez en utilisant , mais il ne contient pas d'autres destinations avec ID R.id.my_next_destination. Ainsi, vous obtenez à IllegalArgumentExceptioncause de l'ID dont le fragment ne sait rien.

Cette erreur exacte peut être trouvée dans la NavController.javaméthode findDestination.


4

Dans mon cas, le problème s'est produit lorsque j'avais réutilisé l'un de mes fragments à l'intérieur d'un viewpagerfragment en tant qu'enfant du viewpager. Le viewpagerfragment (qui était le fragment parent) a été ajouté dans le xml de navigation, mais l'action n'a pas été ajoutée dans le viewpagerfragment parent.

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all">

Correction du problème en ajoutant l'action au fragment de viewpager parent également comme indiqué ci-dessous:

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all"/>
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>

4

Aujourd'hui

def navigationVersion = "2.2.1"

Le problème existe toujours. Mon approche sur Kotlin est:

// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://stackoverflow.com/q/51060762/6352712
fun NavController.navigateSafe(
    @IdRes destinationId: Int,
    navDirection: NavDirections,
    callBeforeNavigate: () -> Unit
) {
    if (currentDestination?.id == destinationId) {
        callBeforeNavigate()
        navigate(navDirection)
    }
}

fun NavController.navigateSafe(@IdRes destinationId: Int, navDirection: NavDirections) {
    if (currentDestination?.id == destinationId) {
        navigate(navDirection)
    }
}

4

Vous pouvez vérifier avant de naviguer si le fragment demandant la navigation est toujours la destination actuelle, tirée de cet élément essentiel .

Il définit essentiellement une balise sur le fragment pour une recherche ultérieure.

/**
 * Returns true if the navigation controller is still pointing at 'this' fragment, or false if it already navigated away.
 */
fun Fragment.mayNavigate(): Boolean {

    val navController = findNavController()
    val destinationIdInNavController = navController.currentDestination?.id
    val destinationIdOfThisFragment = view?.getTag(R.id.tag_navigation_destination_id) ?: destinationIdInNavController

    // check that the navigation graph is still in 'this' fragment, if not then the app already navigated:
    if (destinationIdInNavController == destinationIdOfThisFragment) {
        view?.setTag(R.id.tag_navigation_destination_id, destinationIdOfThisFragment)
        return true
    } else {
        Log.d("FragmentExtensions", "May not navigate: current destination is not the current fragment.")
        return false
    }
}

R.id.tag_navigation_destination_id est juste un identifiant que vous devrez ajouter à votre ids.xml, pour vous assurer qu'il est unique. <item name="tag_navigation_destination_id" type="id" />

Plus d'informations sur le bogue et la solution, et les navigateSafe(...)méthodes d'extention dans "Correction du redouté"… est inconnu de ce NavController "


J'ai étudié différentes solutions à ce problème, et la vôtre est certainement la plus agréable. Cela me rend triste de voir si peu d'amour pour cela
Luc

1
il peut être utile de créer un identifiant unique à la place de NAV_DESTINATION_IDquelque chose comme ce stackoverflow.com/a/15021758/1572848
William Reed

oui, j'ai mis à jour la réponse
Frank le

D'où vient l'étiquette et pourquoi est-elle nécessaire? J'ai des problèmes où l'ID réel sur le composant de navigation ne correspond pas à ceux-ci R.id.
riezebosch le

R.id.tag_navigation_destination_idest juste un identifiant que vous devrez ajouter à votre ids.xml, pour vous assurer qu'il est unique. <item name="tag_navigation_destination_id" type="id" />
Frank

3

Dans mon cas, j'avais plusieurs fichiers de graphique de navigation et j'essayais de passer d'un emplacement de graphique de navigation à une destination dans un autre graphique de navigation.

Pour cela, nous devons inclure le 2ème graphique de navigation dans le 1er comme celui-ci

<include app:graph="@navigation/included_graph" />

et ajoutez ceci à votre action:

<action
        android:id="@+id/action_fragment_to_second_graph"
        app:destination="@id/second_graph" />

second_graphest:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

dans le deuxième graphique.

Plus d'infos ici


3

J'ai résolu le même problème en mettant un chèque avant de naviguer au lieu du code standard pour cliquer instantanément sur le contrôle

 if (findNavController().currentDestination?.id == R.id.currentFragment) {
        findNavController().navigate(R.id.action_current_next)}
/* Here R.id.currentFragment is the id of current fragment in navigation graph */

selon cette réponse

https://stackoverflow.com/a/56168225/7055259


2

Dans mon cas, le bogue est survenu parce que j'avais une action de navigation avec Single Toples Clear Taskoptions et activées après un écran de démarrage.


1
Mais clearTask est obsolète, vous devriez utiliser popUpTo () à la place.
Jerry Oka pour le

@ Po10cio Aucun de ces drapeaux n'était nécessaire, je l'ai juste enlevé et il a été corrigé.
Eury Pérez Beltré

2

J'ai eu cette même erreur parce que j'ai utilisé un tiroir de navigation et getSupportFragmentManager().beginTransaction().replace( )en même temps quelque part dans mon code.

Je me suis débarrassé de l'erreur en utilisant cette condition (tester si la destination):

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

Dans mon cas, l'erreur précédente a été déclenchée lorsque je cliquais sur les options du panneau de navigation. Fondamentalement, le code ci-dessus a masqué l'erreur, car dans mon code quelque part, j'ai utilisé la navigation en utilisant getSupportFragmentManager().beginTransaction().replace( )la condition -

 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

n'a jamais été atteint car il (Navigation.findNavController(v).getCurrentDestination().getId()était toujours en contact avec le fragment de la maison. Vous ne devez utiliser que Navigation.findNavController(v).navigate(R.id.your_action)les fonctions du contrôleur de graphique de navigation ou pour toutes vos actions de navigation.



1

J'ai attrapé cette exception après quelques changements de noms de classes. Par exemple: j'ai eu des cours appelés FragmentAavec@+is/fragment_a dans le graphique de navigation et FragmentBavec @+id/fragment_b. Ensuite , je supprimé FragmentAet retitré FragmentBà FragmentA. Ainsi , après ce nœud de FragmentAtoujours resté dans le graphique de la navigation et android:namedu FragmentBnœud de » a été renommé path.to.FragmentA. J'avais deux nœuds identiques android:nameet différents android:id, et l'action dont j'avais besoin était définie sur le nœud de la classe supprimée.


1

Cela me vient à l'esprit lorsque j'appuie deux fois sur le bouton de retour. Au début, j'intercepte KeyListeneret passe outreKeyEvent.KEYCODE_BACK . J'ai ajouté le code ci-dessous dans la fonction nommée OnResumepour le fragment, puis cette question / 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
        }
    }

Quand cela m'arrive une deuxième fois et que son statut est le même que le premier, je trouve que j'utilise peut-être le adsurd fonction. Analysons ces situations.

  1. Tout d'abord, FragmentA navigue vers FragmentB, puis FragmentB navigue vers FragmentA, puis appuyez sur le bouton Retour ... le crash apparaît.

  2. Deuxièmement, FragmentA navigue vers FragmentB, puis FragmentB navigue vers FragmentC, FragmentC navigue vers FragmentA, puis appuyez sur le bouton Retour ... le crash apparaît.

Donc, je pense qu'en appuyant sur le bouton Retour, FragmentA reviendra à FragmentB ou FragmentC, puis cela provoquera le désordre de connexion. Enfin je trouve que la fonction nomméepopBackStack peut être utilisée pour revenir plutôt que pour naviguer.

  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

Jusqu'à présent, le problème est vraiment résolu.


1

Il semble que le mélange du contrôle fragmentManager de la backstack et du contrôle de l'architecture de navigation de la backstack puisse également causer ce problème.

Par exemple, l'exemple de base CameraX d'origine a utilisé la navigation dans la pile d'arrière-plan fragmentManager comme ci-dessous et il semble qu'il n'interagissait pas correctement avec la navigation:

// Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            fragmentManager?.popBackStack()
        }

Si vous enregistrez la `` destination actuelle '' avec cette version avant de passer du fragment principal (le fragment de caméra dans ce cas) et que vous la consignez à nouveau lorsque vous revenez au fragment principal, vous pouvez voir à partir de l'id dans les journaux que l'id ce n'est pas la même chose. À une supposition, la navigation l'a mis à jour lors du déplacement vers le fragment et le fragmntManager ne l'a pas mis à jour à nouveau lors du recul. À partir des journaux:

Avant : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Après : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

La version mise à jour de l'exemple de base de CameraX utilise Navigation pour revenir comme ceci:

 // Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
        }

Cela fonctionne correctement et les journaux affichent le même identifiant lorsqu'ils reviennent au fragment principal.

Avant : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Après : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Je soupçonne que la morale de l'histoire, du moins en ce moment, est d'être très prudent en mélangeant la navigation avec la navigation fragmentManager.


Cela semble plausible, je vais approfondir mes recherches. Quelqu'un a-t-il pu vérifier ou étayer cette affirmation?
Jerry Oka pour

@JerryOkafor - Je l'ai testé dans une application sur laquelle je travaillais basée sur CameraX Sample et je l'ai vérifié, mais ce serait bien de voir si quelqu'un d'autre l'a également vu. En fait, j'ai manqué une `` navigation arrière '' à un endroit dans la même application, alors je l'ai encore corrigée récemment.
Mick

1

Une manière ridicule mais très puissante est: Appelez simplement ceci:

view?.findNavController()?.navigateSafe(action)

Créez simplement cette extension:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}

1

Il peut y avoir plusieurs raisons à ce problème. Dans mon cas, j'utilisais le modèle MVVM et j'observais un booléen pour la navigation lorsque le booléen est vrai -> naviguer ailleurs ne fait rien et cela fonctionnait bien mais il y avait une erreur ici

lorsque j'appuyais sur le bouton de retour à partir du fragment de destination, je rencontrais le même problème.Et le problème était l'objet booléen car j'ai oublié de changer la valeur booléenne en faux, cela a créé le désordre.Je viens de créer une fonction dans viewModel pour changer sa valeur en faux et l'a appelé juste après le findNavController ()


1

Habituellement, lorsque cela m'arrive, j'ai eu le problème décrit par Charles Madere: deux événements de navigation déclenchés sur la même interface utilisateur, l'un modifiant la destination actuelle et l'autre échouant car la destination actuelle est modifiée. Cela peut se produire si vous appuyez deux fois ou cliquez sur deux vues avec un écouteur de clic appelant findNavController.navigate.

Donc, pour résoudre ce problème, vous pouvez utiliser if-checks, try-catch ou si vous êtes intéressé, il existe un findSafeNavController () qui effectue cette vérification pour vous avant de naviguer. Il dispose également d'une vérification des peluches pour vous assurer de ne pas oublier ce problème.

GitHub

Article détaillant le problème


1

Si vous cliquez trop rapidement, cela entraînera une nullité et un crash.

Nous pouvons utiliser RxBinding lib pour vous aider. Vous pouvez ajouter une accélération et une durée au clic avant qu'il ne se produise.

 RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
            .subscribe(__ -> {
            });

Ces articles sur la limitation sur Android peuvent vous aider. À votre santé!


1

Si vous utilisez un recyclerview, ajoutez simplement un temps de recharge d'écoute de clic sur votre clic et également dans votre utilisation du fichier xml de recyclerview android:splitMotionEvents="false"


1
Regardez les réponses ci-dessous les miennes
Crazy

1

Après avoir réfléchi aux conseils de Ian Lake dans ce fil Twitter, j'ai proposé l'approche suivante. Ayant NavControllerWrapperdéfini comme tel:

class NavControllerWrapper constructor(
  private val navController: NavController
) {

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int
  ) = navigate(
    from = from,
    to = to,
    bundle = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?
  ) = navigate(
    from = from,
    to = to,
    bundle = bundle,
    navOptions = null,
    navigatorExtras = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(
        to,
        bundle,
        navOptions,
        navigatorExtras
      )
    }
  }

  fun navigate(
    @IdRes from: Int,
    directions: NavDirections
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(directions)
    }
  }

  fun navigateUp() = navController.navigateUp()

  fun popBackStack() = navController.popBackStack()
}

Puis dans le code de navigation:

val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)

0

Cela m'est arrivé, mon problème était que je cliquais sur un FAB tab item fragment. J'essayais de naviguer de l'un des fragments d'élément d'onglet vers another fragment.

Mais selon Ian Lake dans cette réponse, nous devons utiliser tablayoutet viewpager, aucun support de composant de navigation . Pour cette raison, il n'y a pas de chemin de navigation entre le tablayout contenant le fragment et le fragment d'élément d'onglet.

ex:

containing fragment -> tab layout fragment -> tab item fragment -> another fragment

La solution était de créer un chemin à partir de la mise en page de l'onglet contenant le fragment vers le fragment voulu ex: chemin: container fragment -> another fragment

Désavantage:

  • Le graphique de navigation ne représente plus le flux utilisateur avec précision.

0

Dans mon cas, j'ai eu cette erreur en essayant de naviguer à partir d'un autre thread, dans 50% des cas. Exécuter le code sur le thread principal aide

requireActivity().runOnUiThread {
    findNavController().navigate(...)
}

Je voudrais voir plus de votes à ce sujet, cela semble plausible mais je ne peux pas le vérifier.
Jerry

0

Dans mon cas, cela s'est produit lorsque j'ai accidentellement ajouté une +destination en action, et le crash ne s'est produit que lorsque je suis allé plusieurs fois sur le même fragment.

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@+id/profileFragment" />

La solution consiste à supprimer +de la destination de l'action, à utiliser uniquement à la @id/profileFragmentplace de@+id/profileFragment

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@id/profileFragment" />

0

Mise à jour de la solution @Alex Nuts

S'il n'y a aucune action pour un fragment particulier et que vous souhaitez accéder au fragment

fun NavController.navigateSafe(
@IdRes actionId: Int, @IdRes fragmentId: Int, args: Bundle? = null,
navOptions: NavOptions? = null, navExtras: Navigator.Extras? = null) 
{
  if (actionId != 0) {
      val action = currentDestination?.getAction(actionId) ?: graph.getAction(actionId)
      if (action != null && currentDestination?.id != action.destinationId) {
          navigate(actionId, args, navOptions, navExtras)
    }
    } else if (fragmentId != 0 && fragmentId != currentDestination?.id)
        navigate(fragmentId, args, navOptions, navExtras)
}

0

J'ai écrit ces extensions

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}

0

J'ai créé cette fonction d'extension pour Fragment:

fun Fragment.safeNavigate(
    @IdRes actionId: Int,
    @Nullable args: Bundle? = null,
    @Nullable navOptions: NavOptions? = null,
    @Nullable navigatorExtras: Navigator.Extras? = null
) {
    NavHostFragment.findNavController(this).apply {
        if (currentDestination?.label == this@safeNavigate::class.java.simpleName) {
            navigate(actionId, args, navOptions, navigatorExtras)
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.