Ich habe ein Problem mit der neuen Android-Navigationsarchitekturkomponente, wenn ich versuche, von einem Fragment zum anderen zu navigieren. Ich erhalte den folgenden seltsamen Fehler:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
Jede andere Navigation funktioniert gut, außer dieser.
Ich benutze die findNavController()
Funktion von Fragment, um Zugriff auf die NavController
.
Jede Hilfe wird geschätzt.
java
android
kotlin
android-navigation
android-architecture-navigation
Jerry Okafor
quelle
quelle
Antworten:
In meinem Fall tritt dieser Absturz auf, wenn der Benutzer zweimal sehr schnell auf dieselbe Ansicht klickt. Sie müssen also eine Art Logik implementieren, um mehrere schnelle Klicks zu verhindern ... Was sehr ärgerlich ist, aber notwendig erscheint.
Weitere Informationen zum Verhindern dieses Vorgangs finden Sie hier: Android Verhindern des Doppelklicks auf eine Schaltfläche
Edit 19.03.2019 : Nur um ein bisschen mehr zu verdeutlichen, ist dieser Absturz nicht ausschließlich reproduzierbar, wenn man nur "zweimal sehr schnell auf dieselbe Ansicht klickt". Alternativ können Sie einfach zwei Finger verwenden und gleichzeitig auf zwei (oder mehr) Ansichten klicken, wobei jede Ansicht ihre eigene Navigation hat, die sie ausführen würden. Dies ist besonders einfach, wenn Sie eine Liste von Elementen haben. Die obigen Informationen zur Verhinderung mehrerer Klicks behandeln diesen Fall.
Edit 16.04.2020 : Nur für den Fall, dass Sie nicht sonderlich daran interessiert sind, diesen Beitrag zum Stapelüberlauf oben zu lesen, füge ich meine eigene (Kotlin) Lösung hinzu, die ich seit langer Zeit verwende.
OnSingleClickListener.kt
ViewExt.kt
HomeFragment.kt
quelle
Überprüfen Sie dies,
currentDestination
bevor Sie navigieren. Dies kann hilfreich sein.Wenn Sie beispielsweise zwei Fragmentziele im Navigationsdiagramm
fragmentA
undfragmentB
haben und nur eine Aktion vonfragmentA
bis vorhanden istfragmentB
. Ein Anrufnavigate(R.id.action_fragmentA_to_fragmentB)
führt dazu,IllegalArgumentException
wenn Sie bereits eingeschaltet warenfragmentB
. Überprüfen Sie dahercurrentDestination
vor dem Navigieren immer die .quelle
Sie können die angeforderte Aktion im aktuellen Ziel des Navigationscontrollers überprüfen.
UPDATE hat die Verwendung globaler Aktionen für eine sichere Navigation hinzugefügt.
quelle
currentDestination
Aktionsliste der Aktion definiert sind . Angenommen, Sie haben eine globale Aktion definiert und verwenden diese Aktion zum Navigieren. Dies schlägt fehl, da die Aktion nicht in der <Aktion> -Liste von currentDestination definiert ist. Das Hinzufügen eines Schecks wiecurrentDestination?.getAction(resId) != null || currentDestination?.id != resId
sollte das Problem beheben, deckt jedoch möglicherweise nicht jeden Fall ab.?: graph.getAction(resId)
->currentDestination?.getAction(resId)
wird eine Aktion für globale oder nicht globale Aktionen zurückgeben (ich habe es getestet). Wäre auch besser, wenn Sie Safe Args verwenden -> liebernavDirections: NavDirections
alsresId
undargs
separat übergeben.Es kann auch vorkommen, dass Sie ein Fragment A mit einem ViewPager von Fragmenten B haben und versuchen, von B nach C zu navigieren
Da die Fragmente im ViewPager kein Ziel von A sind, würde Ihr Diagramm nicht wissen, dass Sie sich auf B befinden.
Eine Lösung kann darin bestehen, ADirections in B zu verwenden, um zu C zu navigieren
quelle
(parentFragment as? XActionListener)?.Xaction()
und zu beachten, dass Sie diese Funktion als lokale Variable halten können, wenn dies hilfreich istWas ich getan habe, um den Absturz zu verhindern, ist Folgendes:
Ich habe ein BaseFragment, in dem ich dieses hinzugefügt habe,
fun
um sicherzustellen, dass dasdestination
bekannt ist durchcurrentDestination
:Bemerkenswert ist, dass ich das SafeArgs- Plugin verwende.
quelle
In meinem Fall habe ich eine benutzerdefinierte Zurück-Schaltfläche zum Navigieren verwendet. Ich habe
onBackPressed()
anstelle des folgenden Codes angerufenDies führte dazu, dass das
IllegalArgumentException
auftrat. Nachdem ich es geändert hatte, um stattdessen dienavigateUp()
Methode zu verwenden , hatte ich keinen Absturz mehr.quelle
TL; DR Schließen Sie Ihre
navigate
Anrufe mittry-catch
(auf einfache Weise) ab oder stellen Sie sicher, dassnavigate
in kurzer Zeit nur ein Anruf eingeht. Dieses Problem wird wahrscheinlich nicht verschwinden. Kopieren Sie ein größeres Code-Snippet in Ihre App und probieren Sie es aus.Hallo. Basierend auf einigen nützlichen Antworten möchte ich meine Lösung teilen, die erweitert werden kann.
Hier ist der Code, der diesen Absturz in meiner Anwendung verursacht hat:
Eine Möglichkeit, den Fehler einfach zu reproduzieren, besteht darin, mit mehreren Fingern auf die Liste der Elemente zu tippen, wobei das Klicken auf jedes Element in der Navigation zum neuen Bildschirm aufgelöst wird (im Grunde das Gleiche wie die angegebenen Personen - zwei oder mehr Klicks in sehr kurzer Zeit ). Ich habe bemerkt, dass:
navigate
Aufruf funktioniert immer einwandfrei.navigate
Methode werden in aufgelöstIllegalArgumentException
.Aus meiner Sicht kann diese Situation sehr oft auftreten. Da das Wiederholen von Code eine schlechte Praxis ist und es immer gut ist, einen Einflusspunkt zu haben, dachte ich an die nächste Lösung:
}}
Und so ändert sich der obige Code nur in einer Zeile davon:
dazu:
Es wurde sogar etwas kürzer. Der Code wurde genau an der Stelle getestet, an der der Absturz aufgetreten ist. Ich habe es nicht mehr erlebt und werde dieselbe Lösung für andere Navigationen verwenden, um denselben Fehler weiter zu vermeiden.
Irgendwelche Gedanken sind willkommen!
Was genau den Absturz verursacht
Denken Sie daran, dass wir hier mit demselben Navigationsdiagramm, Navigationscontroller und Backstack arbeiten, wenn wir die Methode verwenden
Navigation.findNavController
.Wir bekommen hier immer den gleichen Controller und die gleiche Grafik. Wann
navigate(R.id.my_next_destination)
als Graph bezeichnet wird, ändert sich der Backstack fast sofort, während die Benutzeroberfläche noch nicht aktualisiert wurde. Nur nicht schnell genug, aber das ist ok. Nachdem sich der Backstack geändert hat, erhält das Navigationssystem den zweitennavigate(R.id.my_next_destination)
Anruf. Da sich der Backstack geändert hat, arbeiten wir jetzt relativ zum obersten Fragment im Stack. Das oberste Fragment ist das Fragment, zu dem Sie mit navigierenR.id.my_next_destination
, es enthält jedoch keine weiteren Ziele mit IDR.id.my_next_destination
. So erhalten SieIllegalArgumentException
aufgrund der ID, von der das Fragment nichts weiß.Dieser genaue Fehler kann in der
NavController.java
Methode gefunden werdenfindDestination
.quelle
In meinem Fall trat das Problem auf, als ich
viewpager
als Kind des eines meiner Fragmente in einem Fragment wiederverwendet hatteviewpager
. Dasviewpager
Fragment (das das übergeordnete Fragment war) wurde in der Navigations-XML hinzugefügt, die Aktion wurde jedoch nicht imviewpager
übergeordneten Fragment hinzugefügt .Das Problem wurde behoben, indem die Aktion auch wie unten gezeigt zum übergeordneten Viewpager-Fragment hinzugefügt wurde:
quelle
Heute
Das Problem besteht weiterhin. Mein Ansatz bei Kotlin ist:
quelle
Sie können vor der Navigation überprüfen , ob das Fragment der Aufforderung an der Navigation noch das aktuelle Ziel ist, von diesem Kern genommen .
Grundsätzlich wird dem Fragment ein Tag für die spätere Suche zugewiesen.
R.id.tag_navigation_destination_id
ist nur eine ID, die Sie zu Ihrer ids.xml hinzufügen müssen, um sicherzustellen, dass sie eindeutig ist.<item name="tag_navigation_destination_id" type="id" />
Weitere Informationen zum Fehler und zur Lösung sowie zu
navigateSafe(...)
Erweiterungsmethoden unter "Beheben der gefürchteten" ... ist diesem NavController unbekannt. "quelle
NAV_DESTINATION_ID
mit so etwas wie dieser stackoverflow.com/a/15021758/1572848R.id
.R.id.tag_navigation_destination_id
ist nur eine ID, die Sie zu Ihrer ids.xml hinzufügen müssen, um sicherzustellen, dass sie eindeutig ist.<item name="tag_navigation_destination_id" type="id" />
In meinem Fall hatte ich mehrere Navigationsdiagrammdateien und habe versucht, von einem Navigationsdiagrammort zu einem Ziel in einem anderen Navigationsdiagramm zu wechseln.
Dazu müssen wir das 2. Navigationsdiagramm wie folgt in das 1. aufnehmen
und füge dies deiner Aktion hinzu:
wo
second_graph
ist:in der zweiten Grafik.
Mehr Infos hier
quelle
In meinem Fall trat der Fehler auf, weil ich eine Navigationsaktion mit den
Single Top
und denClear Task
Optionen nach einem Begrüßungsbildschirm aktiviert hatte.quelle
Ich habe den gleichen Fehler erhalten, weil ich eine Navigationsschublade verwendet habe und
getSupportFragmentManager().beginTransaction().replace( )
gleichzeitig irgendwo in meinem Code.Ich habe den Fehler mithilfe dieser Bedingung behoben (Testen, ob das Ziel vorhanden ist):
In meinem Fall wurde der vorherige Fehler ausgelöst, als ich auf die Optionen der Navigationsleiste geklickt habe. Grundsätzlich hat der obige Code den Fehler ausgeblendet, da ich in meinem Code irgendwo die Navigation mit
getSupportFragmentManager().beginTransaction().replace( )
der Bedingung - verwendet habe.wurde nie erreicht, weil
(Navigation.findNavController(v).getCurrentDestination().getId()
immer zu Hause Fragment Poiting war. Sie dürfenNavigation.findNavController(v).navigate(R.id.your_action)
für alle Ihre Navigationsaktionen nur Graph Graph Controller-Funktionen verwenden oder navigieren.quelle
Es scheint, als würden Sie die Aufgabe klären. Eine App verfügt möglicherweise über eine einmalige Einrichtung oder eine Reihe von Anmeldebildschirmen. Diese bedingten Bildschirme sollten nicht als Startziel Ihrer App betrachtet werden.
https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional
quelle
Ich habe diese Ausnahme nach einigen Umbenennungen von Klassen abgefangen. Zum Beispiel: Ich hatte Klassen
FragmentA
mit@+is/fragment_a
im Navigationsdiagramm undFragmentB
mit aufgerufen@+id/fragment_b
. Dann habe ich gelöschtFragmentA
und umbenanntFragmentB
inFragmentA
. DanachFragmentA
blieb der Knoten von noch im Navigationsdiagramm undandroid:name
derFragmentB
Knoten von wurde umbenanntpath.to.FragmentA
. Ich hatte zwei Knoten mit demselbenandroid:name
und unterschiedlichem Wertandroid:id
, und die Aktion, die ich benötigte, wurde auf dem Knoten der entfernten Klasse definiert.quelle
Es fällt mir ein, wenn ich zweimal die Zurück-Taste drücke. Zuerst fange ich ab
KeyListener
und überschreibeKeyEvent.KEYCODE_BACK
. Ich habe den folgenden Code in dieOnResume
für das Fragment benannte Funktion eingefügt , und dann ist diese Frage / dieses Problem gelöst.Wenn es mir ein zweites Mal passiert und der Status mit dem ersten identisch ist, stelle ich fest, dass ich die
adsurd
Funktion möglicherweise verwende . Lassen Sie uns diese Situationen analysieren.Zuerst navigiert FragmentA zu FragmentB, dann navigiert FragmentB zu FragmentA und drückt dann die Zurück-Taste ... der Absturz wird angezeigt.
Zweitens navigiert FragmentA zu FragmentB, dann navigiert FragmentB zu FragmentC, FragmentC navigiert zu FragmentA und drückt dann die Zurück-Taste ... der Absturz wird angezeigt.
Ich denke also, wenn FragmentA die Zurück-Taste drückt, kehrt es zu FragmentB oder FragmentC zurück und verursacht dann das Anmelde-Chaos. Schließlich finde ich, dass die genannte Funktion
popBackStack
eher für den Rücken als für die Navigation verwendet werden kann.Bisher ist das Problem wirklich gelöst.
quelle
Es scheint, dass das Mischen der fragmentManager-Steuerung des Backstacks und der Navigationsarchitektursteuerung des Backstacks ebenfalls dieses Problem verursachen kann.
Im ursprünglichen CameraX-Basisbeispiel wurde beispielsweise die FragmentManager-Backstack-Navigation wie folgt verwendet, und es sieht so aus, als ob sie nicht korrekt mit der Navigation interagiert hat:
Wenn Sie das 'aktuelle Ziel' mit dieser Version protokollieren, bevor Sie vom Hauptfragment (in diesem Fall dem Kamerafragment) wechseln und es dann erneut protokollieren, wenn Sie zum Hauptfragment zurückkehren, können Sie anhand der ID in den Protokollen erkennen, dass die ID vorhanden ist ist nicht das Gleiche. Vermutlich hat die Navigation es beim Verschieben in das Fragment aktualisiert, und der fragmntManager hat es beim Zurückbewegen nicht erneut aktualisiert. Aus den Protokollen:
Die aktualisierte Version des CameraX-Basisbeispiels verwendet Navigation, um wie folgt zurückzukehren:
Dies funktioniert ordnungsgemäß und die Protokolle zeigen dieselbe ID, wenn Sie sich wieder im Hauptfragment befinden.
Ich vermute, dass die Moral der Geschichte zumindest zu diesem Zeitpunkt darin besteht, Navigation sehr sorgfältig mit fragmentManager-Navigation zu mischen.
quelle
Ein lächerlicher, aber sehr mächtiger Weg ist: Nennen Sie dies einfach:
Erstellen Sie einfach diese Erweiterung:
quelle
Es kann viele Gründe für dieses Problem geben. In meinem Fall habe ich das MVVM-Modell verwendet und einen Booleschen Wert für die Navigation beobachtet, wenn der Boolesche Wert wahr ist -> navigieren, sonst nichts tun und das hat gut funktioniert, aber hier gab es einen Fehler
Beim Drücken der Zurück-Taste vom Zielfragment stieß ich auf dasselbe Problem. Und das Problem war das boolesche Objekt, da ich vergessen hatte, den booleschen Wert in false zu ändern. Dies verursachte das Durcheinander. Ich habe gerade eine Funktion in viewModel erstellt, um den Wert in false und zu ändern nannte es kurz nach dem findNavController ()
quelle
Wenn mir dies passiert, hatte ich normalerweise das von Charles Madere beschriebene Problem: Zwei Navigationsereignisse, die auf derselben Benutzeroberfläche ausgelöst wurden, wobei eines das aktuelle Ziel ändert und das andere fehlschlägt, weil das aktuelle Ziel geändert wird. Dies kann passieren, wenn Sie zweimal tippen oder mit einem Klick-Listener, der findNavController.navigate aufruft, auf zwei Ansichten klicken.
Um dies zu beheben, können Sie entweder If-Checks oder Try-Catch verwenden. Wenn Sie interessiert sind, gibt es einen findSafeNavController (), der diese Checks vor der Navigation für Sie durchführt. Es gibt auch einen Flusen-Check, um sicherzustellen, dass Sie dieses Problem nicht vergessen.
GitHub
Artikel über das Thema
quelle
Nachdem ich über den Rat von Ian Lake in diesem Twitter-Thread nachgedacht habe, habe ich mir folgenden Ansatz ausgedacht . Als
NavControllerWrapper
solche definiert:Dann im Navigationscode:
quelle
Ich habe das gleiche Problem gelöst, indem ich vor dem Navigieren anstelle des Boilerplate-Codes ein Häkchen gesetzt habe, um sofort auf die Steuerung zu klicken
nach dieser Antwort
https://stackoverflow.com/a/56168225/7055259
quelle
Das ist mir passiert, mein Problem war, dass ich auf ein FAB geklickt habe
tab item fragment
. Ich habe versucht, von einem der Registerkartenelementfragmente zu zu navigierenanother fragment
.Aber nach Ian See in dieser Antwort müssen wir nutzen
tablayout
undviewpager
, keine Navigationskomponente Unterstützung . Aus diesem Grund gibt es keinen Navigationspfad von Tablayout mit Fragment zu Tab-Element-Fragment.Ex:
Die Lösung bestand darin, einen Pfad vom Registerkartenlayout mit dem Fragment zum beabsichtigten Fragment zu erstellen. Beispiel: Pfad:
container fragment -> another fragment
Nachteil:
quelle
In meinem Fall wurde dieser Fehler in 50% der Fälle angezeigt, als ich versuchte, von einem anderen Thread aus zu navigieren. Führen Sie den Code im Hauptthread aus
quelle
In meinem Fall trat dies auf, als ich versehentlich ein
+
Ziel in Aktion hinzufügte , und der Absturz trat nur auf, wenn ich mehrmals zu demselben Fragment ging.Die Lösung besteht darin,
+
vom Aktionsziel zu entfernen und nur@id/profileFragment
anstelle von zu verwenden@+id/profileFragment
quelle
Aktualisierte @ Alex Nuts-Lösung
Wenn für ein bestimmtes Fragment keine Aktion vorhanden ist und Sie zum Fragment navigieren möchten
quelle
Ich habe diese Erweiterungen geschrieben
quelle
Ich habe diese Erweiterungsfunktion für Fragment erstellt:
quelle
Wenn Sie zu schnell auf klicken, führt dies zu null und zum Absturz.
Wir können RxBinding lib verwenden, um dies zu unterstützen. Sie können beim Klicken Gas und Dauer hinzufügen, bevor dies geschieht.
Diese Artikel zum Drosseln unter Android könnten helfen. Prost!
quelle
Wenn Sie eine Recyclerview verwenden, fügen Sie einfach eine Abklingzeit für den Klick-Listener bei Ihrem Klick hinzu und verwenden Sie diese auch in Ihrer Recyclerview-XML-Datei
android:splitMotionEvents="false"
quelle