IllegalArgumentException: Das Navigationsziel xxx ist diesem NavController unbekannt


136

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.


Bitte geben Sie zum besseren Verständnis einen Code an.
Alex

12
Das passiert mir auch.
Eury Pérez Beltré

Bisher wurde die Häufigkeit des Auftretens dieses Fehlers durch neuere Versionen der Bibliothek verringert, aber ich denke, die Bibliothek ist noch nicht gut dokumentiert.
Jerry Okafor

Antworten:


75

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

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))
}

HomeFragment.kt

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

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}

23
Die Bearbeitung über die Verwendung von 2 Fingern und das gleichzeitige Klicken auf 2 Ansichten! Das ist der Schlüssel für mich und hat mir geholfen, das Problem einfach zu replizieren. Tolle Aktualisierung mit diesen Informationen.
Richard Le Mesurier

Während der Debug-Phase habe ich zufällig geklickt, während die App feststeckte und darauf wartete, die Ausführung fortzusetzen. Scheint wie ein weiterer Fall von zwei aufeinander folgenden Klicks hintereinander auf die IDE
Marco

1
Danke dafür. Hat mir ein paar Stürze und ein paar
Kopfkratzer

58

Überprüfen Sie dies, currentDestinationbevor Sie navigieren. Dies kann hilfreich sein.

Wenn Sie beispielsweise zwei Fragmentziele im Navigationsdiagramm fragmentAund fragmentBhaben und nur eine Aktion von fragmentAbis vorhanden ist fragmentB. Ein Anruf navigate(R.id.action_fragmentA_to_fragmentB)führt dazu, IllegalArgumentExceptionwenn Sie bereits eingeschaltet waren fragmentB. Überprüfen Sie daher currentDestinationvor dem Navigieren immer die .

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

3
Ich habe eine Such-App, die mit einer Aktion mit Argumenten navigiert. Somit könnte es vom aktuellen Ziel zu sich selbst navigieren. Am Ende habe ich dasselbe gemacht, außer navController.currentDestination == navController.graph.node. Es fühlte sich allerdings etwas schmutzig an und ich habe das Gefühl, ich sollte das nicht tun müssen.
Shawn Maybush

84
Die Bibliothek sollte uns nicht zwingen, diese Überprüfung durchzuführen, es ist in der Tat lächerlich.
DaniloDeQueiroz

Ich hatte das gleiche Problem. Ich hatte einen EditText und eine Schaltfläche zum Speichern, um den Inhalt des EditText in der Datenbank zu speichern. Es stürzte immer beim Drücken der Schaltfläche "Speichern" ab. Ich vermute, dass der Grund damit zusammenhängt, dass ich die Bildschirmtastatur durch Tippen auf die Zurück-Taste entfernen muss, um die Schaltfläche "Speichern" drücken zu können.
Der Fuchs

Dies prüft auf einen Fehlerzustand, löst das Problem jedoch nicht. Interessanterweise ist diese Bedingung erfüllt, wenn der Navigations-Backstack aus unerwünschten Gründen leer wird.
Mike76

1
Selbst unter iOS werden manchmal mehrere ViewController gedrückt, wenn Sie die Taste mehrmals drücken. Vermutlich haben sowohl Android als auch iOS dieses Problem.
coolcool1994

47

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.

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
Diese Lösung funktioniert nicht für Aktionen, die außerhalb der currentDestinationAktionsliste 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 wie currentDestination?.getAction(resId) != null || currentDestination?.id != resIdsollte das Problem beheben, deckt jedoch möglicherweise nicht jeden Fall ab.
wchristiansen

@wchristiansen, danke für Notizen. Ich habe Code unter Verwendung globaler Aktionen aktualisiert
Alex Nuts

@ AlexNuts tolle Antwort. Ich denke, Sie können entfernen ?: 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 -> lieber navDirections: NavDirectionsals resIdund argsseparat übergeben.
Wess

@AlexNuts Beachten Sie, dass diese Lösung das Navigieren zum selben Ziel wie das aktuelle Ziel nicht unterstützt. Das Navigieren von Ziel X mit Bundle Y zu Ziel X mit Bundle Z ist nicht möglich.
Wess

18

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


In diesem Fall tritt der Absturz nicht jedes Mal auf, sondern nur selten. Wie man es löst?
Srikar Reddy

Sie können eine globale Aktion innerhalb des navGraph hinzufügen und damit navigieren
Abraham Mathew

1
Da B sein genaues übergeordnetes Element nicht kennen muss, ist es besser, ADirections über eine Schnittstelle wie zu verwenden (parentFragment as? XActionListener)?.Xaction()und zu beachten, dass Sie diese Funktion als lokale Variable halten können, wenn dies hilfreich ist
hmac


Jeder könnte einen Beispielcode plz, ich bin bei der gleichen Ausgabe stecken. Haben Sie ein Fragment und dann ein Tabfragment
Usman Zafer

13

Was ich getan habe, um den Absturz zu verhindern, ist Folgendes:

Ich habe ein BaseFragment, in dem ich dieses hinzugefügt habe, funum sicherzustellen, dass das destinationbekannt ist durch currentDestination:

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

Bemerkenswert ist, dass ich das SafeArgs- Plugin verwende.


12

In meinem Fall habe ich eine benutzerdefinierte Zurück-Schaltfläche zum Navigieren verwendet. Ich habe onBackPressed()anstelle des folgenden Codes angerufen

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

Dies führte dazu, dass das IllegalArgumentExceptionauftrat. Nachdem ich es geändert hatte, um stattdessen die navigateUp()Methode zu verwenden , hatte ich keinen Absturz mehr.


Ich verstehe nicht, was der Unterschied zwischen onBackPressed und diesem ist, immer noch bei der System-Zurück-Taste zu bleiben und sie zu überschreiben und durch diese zu ersetzen, scheint verrückt
Daniel Wilson

2
Ich bin damit einverstanden, dass es verrückt erscheint. Viele der Dinge, die ich in der Android-Navigationsarchitektur-Komponente erlebt habe, fühlen sich ein bisschen verrückt an, sie ist IMO zu starr eingerichtet. Ich denke darüber nach, meine eigene Implementierung für unser Projekt zu machen, da es einfach zu viele Kopfschmerzen verursacht
Neil

Funktioniert bei mir nicht ... Immer noch der gleiche Fehler.
Otziii

5

TL; DR Schließen Sie Ihre navigateAnrufe mit try-catch(auf einfache Weise) ab oder stellen Sie sicher, dass navigatein 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:

@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);
}

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:

  1. Der erste navigateAufruf funktioniert immer einwandfrei.
  2. Der zweite und alle anderen Aufrufe der navigateMethode werden in aufgelöst IllegalArgumentException.

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:

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.");
    }
}

}}

Und so ändert sich der obige Code nur in einer Zeile davon:

Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);

dazu:

NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);

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 zweiten navigate(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 navigieren R.id.my_next_destination, es enthält jedoch keine weiteren Ziele mit ID R.id.my_next_destination. So erhalten Sie IllegalArgumentExceptionaufgrund der ID, von der das Fragment nichts weiß.

Dieser genaue Fehler kann in der NavController.javaMethode gefunden werden findDestination.


4

In meinem Fall trat das Problem auf, als ich viewpagerals Kind des eines meiner Fragmente in einem Fragment wiederverwendet hatte viewpager. Das viewpagerFragment (das das übergeordnete Fragment war) wurde in der Navigations-XML hinzugefügt, die Aktion wurde jedoch nicht im viewpagerübergeordneten Fragment hinzugefügt .

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">

Das Problem wurde behoben, indem die Aktion auch wie unten gezeigt zum übergeordneten Viewpager-Fragment hinzugefügt wurde:

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

Heute

def navigationVersion = "2.2.1"

Das Problem besteht weiterhin. Mein Ansatz bei Kotlin ist:

// 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

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.

/**
 * 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 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. "


Ich habe ein paar verschiedene Lösungen für dieses Problem studiert, und Ihre ist definitiv die schönste. Es macht mich traurig, so wenig Liebe dafür zu sehen
Lukas

1
kann es nützlich sein , eine eindeutige Kennung in Ort zu schaffen , der NAV_DESTINATION_IDmit so etwas wie dieser stackoverflow.com/a/15021758/1572848
William Reed

Ja, ich habe die Antwort aktualisiert
Frank

Woher kommt das Tag und warum wird es benötigt? Ich habe Probleme, bei denen die tatsächlichen IDs in der Navigationskomponente nicht mit diesen übereinstimmen R.id.
Riezebosch

R.id.tag_navigation_destination_idist 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" />
Frank

3

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

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

und füge dies deiner Aktion hinzu:

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

wo second_graphist:

<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">

in der zweiten Grafik.

Mehr Infos hier


2

In meinem Fall trat der Fehler auf, weil ich eine Navigationsaktion mit den Single Topund den Clear TaskOptionen nach einem Begrüßungsbildschirm aktiviert hatte.


1
ClearTask ist jedoch veraltet. Verwenden Sie stattdessen popUpTo ().
Jerry Okafor

@ Po10cio Keines dieser Flags wurde benötigt, ich habe es einfach entfernt und es wurde behoben.
Eury Pérez Beltré

2

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):

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

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.

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

wurde nie erreicht, weil (Navigation.findNavController(v).getCurrentDestination().getId()immer zu Hause Fragment Poiting war. Sie dürfen Navigation.findNavController(v).navigate(R.id.your_action)für alle Ihre Navigationsaktionen nur Graph Graph Controller-Funktionen verwenden oder navigieren.



1

Ich habe diese Ausnahme nach einigen Umbenennungen von Klassen abgefangen. Zum Beispiel: Ich hatte Klassen FragmentAmit @+is/fragment_aim Navigationsdiagramm und FragmentBmit aufgerufen @+id/fragment_b. Dann habe ich gelöscht FragmentAund umbenannt FragmentBin FragmentA. Danach FragmentAblieb der Knoten von noch im Navigationsdiagramm und android:nameder FragmentBKnoten von wurde umbenannt path.to.FragmentA. Ich hatte zwei Knoten mit demselben android:nameund unterschiedlichem Wert android:id, und die Aktion, die ich benötigte, wurde auf dem Knoten der entfernten Klasse definiert.


1

Es fällt mir ein, wenn ich zweimal die Zurück-Taste drücke. Zuerst fange ich ab KeyListenerund überschreibe KeyEvent.KEYCODE_BACK. Ich habe den folgenden Code in die OnResumefür das Fragment benannte Funktion eingefügt , und dann ist diese Frage / dieses Problem gelöst.

  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
        }
    }

Wenn es mir ein zweites Mal passiert und der Status mit dem ersten identisch ist, stelle ich fest, dass ich die adsurdFunktion möglicherweise verwende . Lassen Sie uns diese Situationen analysieren.

  1. Zuerst navigiert FragmentA zu FragmentB, dann navigiert FragmentB zu FragmentA und drückt dann die Zurück-Taste ... der Absturz wird angezeigt.

  2. 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 popBackStackeher für den Rücken als für die Navigation verwendet werden kann.

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

Bisher ist das Problem wirklich gelöst.


1

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:

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

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:

Vorher : D / CameraXBasic: currentDest?: Androidx.navigation.fragment.FragmentNavigator$Destination@b713195

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

Die aktualisierte Version des CameraX-Basisbeispiels verwendet Navigation, um wie folgt zurückzukehren:

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

Dies funktioniert ordnungsgemäß und die Protokolle zeigen dieselbe ID, wenn Sie sich wieder im Hauptfragment befinden.

Vorher : D / CameraXBasic: currentDest?: Androidx.navigation.fragment.FragmentNavigator$Destination@b713195

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

Ich vermute, dass die Moral der Geschichte zumindest zu diesem Zeitpunkt darin besteht, Navigation sehr sorgfältig mit fragmentManager-Navigation zu mischen.


Das klingt plausibel, ich werde weiter untersuchen. Hat jemand diese Behauptung überprüfen oder begründen können?
Jerry Okafor

@JerryOkafor - Ich habe es in einer App getestet, an der ich basierend auf CameraX Sample gearbeitet habe, und es überprüft, aber es wäre gut zu sehen, ob dies auch jemand anderes gesehen hat. Ich habe tatsächlich eine "Rückennavigation" an einer Stelle in derselben App verpasst, also habe ich sie kürzlich auch wieder behoben.
Mick

1

Ein lächerlicher, aber sehr mächtiger Weg ist: Nennen Sie dies einfach:

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

Erstellen Sie einfach diese Erweiterung:

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

1

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 ()


1

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


1

Nachdem ich über den Rat von Ian Lake in diesem Twitter-Thread nachgedacht habe, habe ich mir folgenden Ansatz ausgedacht . Als NavControllerWrappersolche definiert:

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()
}

Dann im Navigationscode:

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

1

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

 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 */

nach dieser Antwort

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


0

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 tablayoutund viewpager, keine Navigationskomponente Unterstützung . Aus diesem Grund gibt es keinen Navigationspfad von Tablayout mit Fragment zu Tab-Element-Fragment.

Ex:

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

Die Lösung bestand darin, einen Pfad vom Registerkartenlayout mit dem Fragment zum beabsichtigten Fragment zu erstellen. Beispiel: Pfad: container fragment -> another fragment

Nachteil:

  • Das Navigationsdiagramm stellt den Benutzerfluss nicht mehr genau dar.

0

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

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

Ich würde gerne mehr Stimmen dazu sehen, klingt plausibel, kann es aber nicht verifizieren.
Jerry Okafor

0

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.

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

Die Lösung besteht darin, +vom Aktionsziel zu entfernen und nur @id/profileFragmentanstelle von zu verwenden@+id/profileFragment

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

0

Aktualisierte @ Alex Nuts-Lösung

Wenn für ein bestimmtes Fragment keine Aktion vorhanden ist und Sie zum Fragment navigieren möchten

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

Ich habe diese Erweiterungen geschrieben

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

0

Ich habe diese Erweiterungsfunktion für Fragment erstellt:

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)
        }
    }
}

0

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.

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

Diese Artikel zum Drosseln unter Android könnten helfen. Prost!


0

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"


1
Schauen Sie sich die Antworten unter mir an
Crazy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.