IllegalArgumentException: la destinazione di navigazione xxx è sconosciuta a questo NavController


139

Ho problemi con il nuovo componente Architettura di navigazione Android quando provo a navigare da un frammento a un altro , ricevo questo strano errore:

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

Ogni altra navigazione funziona bene tranne questa in particolare.

Uso la findNavController()funzione di Fragment per accedere a NavController.

Qualsiasi aiuto sarà apprezzato.


Fornisci un codice per una migliore comprensione.
Alex

12
Questo sta accadendo anche a me.
Eury Pérez Beltré

Finora il tasso di occorrenza di questo bug è stato ridotto con le versioni più recenti della libreria, ma penso che la libreria non sia ancora ben documentata.
Jerry Oka per il

Risposte:


76

Nel mio caso, se l'utente fa clic sulla stessa vista due volte molto rapidamente, si verificherà questo arresto anomalo. Quindi è necessario implementare una sorta di logica per impedire più clic rapidi ... Il che è molto fastidioso, ma sembra essere necessario.

Puoi leggere ulteriori informazioni su come prevenirlo qui: Android Prevenzione del doppio clic su un pulsante

Modifica 19/03/2019 : Giusto per chiarire un po 'di più, questo arresto anomalo non è riproducibile esclusivamente "facendo clic due volte sulla stessa vista molto molto rapidamente". In alternativa, puoi semplicemente usare due dita e fare clic su due (o più) viste contemporaneamente, in cui ciascuna vista ha la propria navigazione che avrebbero eseguito. Ciò è particolarmente facile da fare quando si dispone di un elenco di elementi. Le informazioni di cui sopra sulla prevenzione dei clic multipli gestiranno questo caso.

Modifica 16/04/2020 : nel caso in cui non fossi particolarmente interessato a leggere quel post di Stack Overflow sopra, includo la mia soluzione (Kotlin) che utilizzo da molto tempo.

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
La modifica sull'uso di 2 dita e sul clic di 2 visualizzazioni contemporaneamente! Questa è la chiave per me e mi ha aiutato a replicare facilmente il problema. Ottimo aggiornamento con quelle informazioni.
Richard Le Mesurier

Durante la fase di debug mi è capitato di cliccare mentre l'app era bloccata in attesa di proseguire l'esecuzione. Sembra un altro caso di due click successivi di fila all'IDE
Marco

1
Grazie per questo. Mi ha risparmiato alcuni crash e qualche graffio alla testa :)
user2672052

58

Controllare currentDestinationprima di chiamare, navigare potrebbe essere utile.

Ad esempio, se hai due destinazioni di frammento sul grafico di navigazione fragmentAe fragmentB, e c'è solo un'azione da fragmentAa fragmentB. la chiamata navigate(R.id.action_fragmentA_to_fragmentB)risulterà IllegalArgumentExceptionquando eri già attivo fragmentB. Pertanto dovresti sempre controllare currentDestinationprima di navigare.

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

3
Ho un'app di ricerca che naviga con un'azione con argomenti. Quindi potrebbe navigare dalla currentDestination a se stessa. Ho finito per fare lo stesso tranne navController.currentDestination == navController.graph.node. Mi sentivo un po 'sporco però e mi sento come se non dovessi farlo.
Shawn Maybush

85
La biblioteca non dovrebbe obbligarci a fare questo controllo, anzi è ridicolo.
DaniloDeQueiroz

Ho avuto lo stesso problema. Avevo un EditText e un pulsante "salva" per memorizzare il contenuto di EditText nel database. Si è sempre bloccato premendo il pulsante "salva". Sospetto che il motivo abbia a che fare con il fatto che, per poter premere il pulsante "salva", devo sbarazzarmi della tastiera su schermo toccando il pulsante indietro.
The Fox

Ciò verifica la presenza di una condizione di errore, ma non risolve il problema. È interessante notare che questa condizione è vera se il backstack di navigazione diventa vuoto per motivi indesiderati.
Mike76

1
anche in iOS, anche se a volte più ViewController vengono premuti quando si preme il pulsante più volte. Immagino che sia Android che iOS abbiano questo problema.
coolcool1994

47

È possibile controllare l'azione richiesta nella destinazione corrente del controller di navigazione.

UPDATE ha aggiunto l'utilizzo di azioni globali per una navigazione sicura.

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
Questa soluzione non funzionerà per le azioni definite al di fuori currentDestinationdell'elenco delle azioni di. Supponi di aver definito un'azione globale e di utilizzare tale azione per navigare. Questo fallirà perché l'azione non è definita nell'elenco <action> di currentDestination. L'aggiunta di un controllo come currentDestination?.getAction(resId) != null || currentDestination?.id != resIddovrebbe risolverlo, ma potrebbe anche non coprire tutti i casi.
wchristiansen

@wchristiansen, grazie per le note. Ho aggiornato il codice con l'utilizzo di azioni globali
Alex Nuts

@AlexNuts ottima risposta. Penso che tu possa rimuovere ?: graph.getAction(resId)-> currentDestination?.getAction(resId)restituirà un'azione sia per azioni globali che non globali (l'ho testato). Inoltre, sarebbe meglio se si fatto uso di Args sicuro -> piuttosto passare navDirections: NavDirectionsoltre resIde argsseparatamente.
Wess

@AlexNuts Nota che questa soluzione non supporta la navigazione verso la stessa destinazione della destinazione corrente. Iow la navigazione dalla destinazione X con Bundle Y alla destinazione X con Bundle Z non è possibile.
Wess

18

Potrebbe anche accadere se hai un Frammento A con un ViewPager di Frammenti B e provi a navigare da B a C

Poiché nel ViewPager i frammenti non sono una destinazione di A, il tuo grafico non saprebbe che sei su B.

Una soluzione può essere quella di utilizzare ADirections in B per passare a C.


In questo caso, il crash non si verifica ogni volta ma si verifica solo raramente. Come risolverlo?
Srikar Reddy

Puoi aggiungere un'azione globale all'interno del navGraph e usarla per navigare
Abraham Mathew

1
Poiché B non dovrebbe aver bisogno di essere consapevole del suo esatto genitore, sarebbe meglio usare ADirections tramite un'interfaccia come (parentFragment as? XActionListener)?.Xaction()e nota che potresti mantenere questa funzione come variabile locale se ciò è utile
hmac

puoi condividere un codice di esempio per illustrare questo dato che ho lo stesso problema
Ikhiloya Imokhai

chiunque potrebbe plz un codice di esempio, sono bloccato sullo stesso problema. Avere un frammento e poi un tabfragment
Usman Zafer

13

Quello che ho fatto per prevenire l'arresto è il seguente:

Ho un BaseFragment, lì ho aggiunto questo funper assicurarmi che destinationsia conosciuto da currentDestination:

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

Vale la pena notare che sto usando il plugin SafeArgs .


12

Nel mio caso stavo usando un pulsante Indietro personalizzato per navigare verso l'alto. Ho chiamato onBackPressed()al posto del seguente codice

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

Ciò ha causato il IllegalArgumentExceptionverificarsi. Dopo averlo cambiato per utilizzare il navigateUp()metodo invece, non ho avuto più un incidente.


Non capisco quale sia la differenza tra onBackPressed e questo, ancora bloccato con il pulsante Indietro del sistema e sovrascriverlo e sostituirlo con questo sembra folle
Daniel Wilson

2
Sono d'accordo che sembra folle. Molte delle cose che ho riscontrato nel componente dell'architettura di navigazione Android sembrano un po 'pazze, è impostato troppo rigidamente IMO. Sto pensando di realizzare la mia implementazione per il nostro progetto in quanto crea troppi grattacapi
Neil

Non funziona per me ... Continuo a ricevere lo stesso errore.
Otziii

5

TL; DR Concludi le tue navigatechiamate con try-catch(modo semplice), o assicurati che ci sarà solo una chiamata di navigatein un breve periodo di tempo. Questo problema probabilmente non scomparirà. Copia uno snippet di codice più grande nella tua app e provalo.

Ciao. Sulla base di un paio di risposte utili sopra, vorrei condividere la mia soluzione che può essere estesa.

Ecco il codice che ha causato questo arresto anomalo nella mia applicazione:

@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 modo per riprodurre facilmente il bug è toccare con più dita l'elenco di elementi in cui il clic su ciascun elemento si risolve nella navigazione verso la nuova schermata (fondamentalmente lo stesso come notato dalle persone: due o più clic in un periodo di tempo molto breve ). Ho notato che:

  1. Primo navigate chiamata funziona sempre bene;
  2. La seconda e tutte le altre invocazioni del navigatemetodo si risolvono in IllegalArgumentException.

Dal mio punto di vista, questa situazione può apparire molto spesso. Poiché la ripetizione del codice è una cattiva pratica ed è sempre bene avere un punto di influenza, ho pensato alla soluzione successiva:

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

}

E quindi il codice sopra cambia solo in una riga da questo:

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

a questa:

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

È persino diventato un po 'più corto. Il codice è stato testato nel punto esatto in cui si è verificato l'arresto anomalo. Non l'ho più sperimentato e utilizzerò la stessa soluzione per altre navigazioni per evitare ulteriormente lo stesso errore.

Qualsiasi pensiero è benvenuto!

Cosa causa esattamente l'incidente

Ricorda che qui lavoriamo con lo stesso grafico di navigazione, controller di navigazione e back-stack quando usiamo il metodo Navigation.findNavController.

Abbiamo sempre lo stesso controller e grafico qui. Quando navigate(R.id.my_next_destination)viene chiamato grafico e back-stack cambia quasi istantaneamente mentre l'interfaccia utente non è ancora aggiornata. Solo non abbastanza veloce, ma va bene. Dopo che il back-stack è stato modificato, il sistema di navigazione riceve la seconda navigate(R.id.my_next_destination)chiamata. Poiché il back-stack è cambiato, ora operiamo in relazione al primo frammento nello stack. Il frammento superiore è il frammento verso il quale si naviga utilizzando R.id.my_next_destination, ma non contiene altre destinazioni successive con ID R.id.my_next_destination. Quindi ottieni IllegalArgumentExceptionl'ID di cui il frammento non sa nulla.

Questo errore esatto può essere trovato in NavController.javamethod findDestination.


4

Nel mio caso, il problema si è verificato quando avevo riutilizzato uno dei miei frammenti all'interno di un viewpagerframmento da bambino di viewpager. Il viewpagerframmento (che era il frammento padre) è stato aggiunto nell'xml di navigazione, ma l'azione non è stata aggiunta nel viewpagerframmento padre.

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

Risolto il problema aggiungendo l'azione al frammento del viewpager genitore anche come mostrato di seguito:

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

Oggi

def navigationVersion = "2.2.1"

Il problema esiste ancora. Il mio approccio su Kotlin è:

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

Puoi verificare prima della navigazione se il Frammento che richiede la navigazione è ancora la destinazione corrente, tratta da questo gist .

Fondamentalmente imposta un tag sul frammento per la ricerca successiva.

/**
 * 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 è solo un ID che dovrai aggiungere al tuo ids.xml, per assicurarti che sia unico. <item name="tag_navigation_destination_id" type="id" />

Maggiori informazioni sul bug, la soluzione e i navigateSafe(...)metodi di estensione in "Correzione del temuto" ... è sconosciuto a questo NavController "


Ho studiato diverse soluzioni a questo problema e la tua è sicuramente la più carina. Mi rattrista vedere così poco amore per questo
Luca

1
può essere utile creare un identificatore univoco al posto di NAV_DESTINATION_IDqualcosa come questo stackoverflow.com/a/15021758/1572848
William Reed

sì, ho aggiornato la risposta
Frank

Da dove proviene il tag e perché è necessario? Ho problemi in cui gli ID effettivi sul componente di navigazione non corrispondono a questi R.id.
riezebosch

R.id.tag_navigation_destination_idè solo un ID che dovrai aggiungere al tuo ids.xml, per assicurarti che sia unico. <item name="tag_navigation_destination_id" type="id" />
Frank

3

Nel mio caso, avevo più file del grafico di navigazione e stavo cercando di spostarmi da 1 posizione del grafico di navigazione a una destinazione in un altro grafico di navigazione.

Per questo dobbiamo includere il secondo grafico di navigazione nel primo in questo modo

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

e aggiungi questo alla tua azione:

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

dov'è second_graph:

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

nel secondo grafico.

Maggiori info qui


3

Ho risolto lo stesso problema mettendo il controllo prima di navigare invece del codice boilerplate per fare clic sul controllo istantaneo

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

secondo questa risposta

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


2

Nel mio caso il bug si è verificato perché avevo un'azione di navigazione con Single Tope le Clear Taskopzioni abilitate dopo una schermata iniziale.


1
Ma clearTask è deprecato, dovresti invece usare popUpTo ().
Jerry Oka per il

@ Po10cio Nessuno di quei flag era necessario, l'ho appena rimosso ed è stato risolto.
Eury Pérez Beltré

2

Ho ricevuto lo stesso errore perché ho usato un cassetto di navigazione e getSupportFragmentManager().beginTransaction().replace( ) allo stesso tempo da qualche parte nel mio codice.

Mi sono sbarazzato dell'errore usando questa condizione (testando se la destinazione):

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

Nel mio caso l'errore precedente è stato attivato quando stavo facendo clic sulle opzioni del cassetto di navigazione. Fondamentalmente il codice sopra ha nascosto l'errore, perché nel mio codice da qualche parte ho usato la navigazione usando getSupportFragmentManager().beginTransaction().replace( )La condizione -

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

non è mai stato raggiunto perché (Navigation.findNavController(v).getCurrentDestination().getId()era sempre puntato sul frammento di casa. È necessario utilizzare solo Navigation.findNavController(v).navigate(R.id.your_action)o le funzioni del controller del grafico di navigazione per tutte le azioni di navigazione.



1

Ho rilevato questa eccezione dopo alcune ridenominazioni delle classi. Ad esempio: ho avuto classi chiamate FragmentAcon @+is/fragment_anel grafico di navigazione e FragmentBcon @+id/fragment_b. Quindi ho cancellato FragmentAe rinominato FragmentBin FragmentA. Così, dopo che il nodo di FragmentAancora stati in grafo di navigazione, e android:namedi FragmentB's nodo è stato rinominato path.to.FragmentA. Avevo due nodi con lo stesso android:namee diverso android:ide l'azione di cui avevo bisogno era definita sul nodo della classe rimossa.


1

Mi viene in mente quando premo due volte il pulsante Indietro. All'inizio, intercetto KeyListenere sovrascrivo KeyEvent.KEYCODE_BACK. Ho aggiunto il codice seguente nella funzione denominata OnResumeper il frammento, quindi questa domanda / problema è stata risolta.

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

Quando mi succede una seconda volta, e lo stato è lo stesso della prima, scopro che forse uso la adsurdfunzione. Analizziamo queste situazioni.

  1. In primo luogo, FragmentA passa a FragmentB, quindi FragmentB passa a FragmentA, quindi premere il pulsante Indietro ... viene visualizzato il crash.

  2. In secondo luogo, FragmentA passa a FragmentB, quindi FragmentB passa a FragmentC, FragmentC naviga a FragmentA, quindi premere il pulsante Indietro ... viene visualizzato il crash.

Quindi penso che quando si preme il pulsante Indietro, FragmentA tornerà a FragmentB o FragmentC, quindi causerà il disordine di accesso. Infine trovo che la funzione denominata popBackStackpossa essere utilizzata per tornare indietro piuttosto che navigare.

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

Finora, il problema è davvero risolto.


1

Sembra che anche la combinazione del controllo fragmentManager del backstack e del controllo dell'architettura di navigazione del backstack possa causare questo problema.

Ad esempio, l'esempio di base di CameraX originale utilizzava la navigazione backstack di fragmentManager come di seguito e sembra che non interagisse correttamente con la navigazione:

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

Se registri la 'destinazione corrente' con questa versione prima di spostarti dal frammento principale (il frammento della fotocamera in questo caso) e poi la registri di nuovo quando torni al frammento principale, puoi vedere dall'ID nei log che non è lo stesso. Probabilmente, la navigazione lo aggiornava quando si spostava sul frammento e fragmntManager non lo aggiornava di nuovo quando tornava indietro. Dai log:

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

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

La versione aggiornata dell'esempio di base di CameraX utilizza Navigation per restituire in questo modo:

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

Funziona correttamente e i log mostrano lo stesso ID quando tornano al frammento principale.

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

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

Sospetto che la morale della storia, almeno in questo momento, sia di stare molto attenti a mescolare la navigazione con la navigazione fragmentManager.


Questo sembra plausibile, indagherò ulteriormente. Qualcuno è stato in grado di verificare o motivare questa affermazione?
Jerry Oka per

@ JerryOkafor - L'ho testato in un'app su cui stavo lavorando sulla base di CameraX Sample e l'ho verificato, ma sarebbe bello vedere se qualcun altro ha visto anche questo. In realtà ho perso una "navigazione indietro" in un punto della stessa app, quindi l'ho riparata di nuovo di recente.
Mick

1

Un modo ridicolo ma molto potente è: Chiamalo semplicemente:

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

Basta creare questa estensione:

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

1

Potrebbero esserci molte ragioni per questo problema. Nel mio caso stavo usando il modello MVVM e stavo osservando un booleano per la navigazione quando il booleano è vero -> naviga altro non fare nulla e questo funzionava bene ma c'è stato un errore qui

quando si premeva il pulsante Indietro dal frammento di destinazione ho riscontrato lo stesso problema.E il problema era l'oggetto booleano poiché ho dimenticato di cambiare il valore booleano in false, questo ha creato il pasticcio.Ho appena creato una funzione in viewModel per cambiare il suo valore in false e chiamato subito dopo findNavController ()


1

Di solito quando questo accade a me, ho avuto il problema descritto da Charles Madere: due eventi di navigazione attivati ​​sulla stessa interfaccia utente, uno che cambia la currentDestination e l'altro fallisce perché la currentDestination è cambiata. Ciò può accadere se si tocca due volte o si fa clic su due visualizzazioni con un listener di clic che chiama findNavController.navigate.

Quindi per risolvere questo problema puoi usare if-checks, try-catch o se sei interessato c'è un findSafeNavController () che fa questo controllo per te prima di navigare. Ha anche un controllo dei pelucchi per assicurarti di non dimenticare questo problema.

GitHub

Articolo che descrive in dettaglio il problema


1

Se fai clic su troppo velocemente, causerà null e crash.

Possiamo usare RxBinding lib per aiutare in questo. È possibile aggiungere throttle e durata al clic prima che avvenga.

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

Questi articoli sulla limitazione su Android potrebbero essere utili. Saluti!


1

Se stai usando un recyclerview, aggiungi semplicemente un tempo di recupero del click listener sul tuo clic e anche nel tuo recyclerview usa il file xml android:splitMotionEvents="false"


1
Guarda le risposte sotto le mie
Crazy

1

Dopo aver riflettuto sul consiglio di Ian Lake in questo thread di Twitter, ho trovato il seguente approccio. Avendo NavControllerWrapperdefinito come tale:

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

Quindi nel codice di navigazione:

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

0

Questo è successo a me, il mio problema era che stavo facendo clic su un FAB su tab item fragment. Stavo cercando di navigare da uno dei frammenti di elementi della scheda a another fragment.

Ma secondo Ian Lake in questa risposta dobbiamo usare tablayoute viewpager, nessun supporto per i componenti di navigazione . Per questo motivo, non esiste alcun percorso di navigazione dal tablayout contenente un frammento al frammento dell'elemento della scheda.

ex:

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

La soluzione era creare un percorso dal layout della scheda contenente il frammento al frammento previsto, ad esempio: percorso: container fragment -> another fragment

Svantaggio:

  • Il grafico di navigazione non rappresenta più il flusso dell'utente in modo accurato.

0

Nel mio caso ho ricevuto quell'errore quando ho provato a navigare da un altro thread, nel 50% dei casi. Eseguire il codice sul thread principale aiuta

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

Vorrei vedere più voti su questo, sembra plausibile ma non posso verificarlo.
Jerry Oka per il

0

Nel mio caso ciò si è verificato quando ho aggiunto accidentalmente una +destinazione in azione e l'arresto anomalo si è verificato solo quando sono andato più volte allo stesso frammento.

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

La soluzione è rimuovere +dalla destinazione dell'azione, utilizzare solo @id/profileFragmentinvece di@+id/profileFragment

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

0

Aggiornata la soluzione @Alex Nuts

Se non è presente alcuna azione per un frammento particolare e si desidera passare al frammento

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

Ho scritto queste estensioni

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

0

Ho creato questa funzione di estensione per 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)
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.