IllegalArgumentException: el destino de navegación xxx es desconocido para este NavController


138

Tengo problemas con el nuevo componente de Arquitectura de navegación de Android cuando intento navegar de un Fragmento a otro , obtengo este extraño error:

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

Cualquier otra navegación funciona bien, excepto esta en particular.

Uso la findNavController()función de Fragmento para obtener acceso a NavController.

Cualquier ayuda será apreciada.


Proporcione un código para una mejor comprensión.
Alex

12
Esto me está pasando a mí también.
Eury Pérez Beltré

Hasta ahora, la tasa de aparición de este error se ha reducido con las nuevas versiones de la biblioteca, pero creo que la biblioteca aún no está bien documentada.
Jerry Okafor

Respuestas:


75

En mi caso, si el usuario hace clic en la misma vista dos veces muy rápidamente, se producirá este bloqueo. Por lo tanto, debe implementar algún tipo de lógica para evitar múltiples clics rápidos ... Lo cual es muy molesto, pero parece ser necesario.

Puede leer más sobre cómo prevenir esto aquí: Android Prevención de doble clic en un botón

Edición 19/03/2019 : Solo para aclarar un poco más, este bloqueo no es exclusivamente reproducible simplemente "haciendo clic en la misma vista dos veces muy rápidamente". Alternativamente, puede usar dos dedos y hacer clic en dos (o más) vistas al mismo tiempo, donde cada vista tiene su propia navegación que realizarían. Esto es especialmente fácil de hacer cuando tienes una lista de artículos. La información anterior sobre prevención de clics múltiples manejará este caso.

Edición 16/04/2020 : en caso de que no estés terriblemente interesado en leer esa publicación de Stack Overflow anterior, incluyo mi propia solución (Kotlin) que he estado usando durante mucho tiempo.

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 edición sobre usar 2 dedos y hacer clic en 2 vistas al mismo tiempo! Esa es la clave para mí y me ayudó a replicar el problema fácilmente. Gran actualización con esa información.
Richard Le Mesurier

Durante la fase de depuración, hice clic mientras la aplicación estaba atascada esperando continuar con la ejecución. Parece otro caso de dos clics consecutivos en el IDE
Marco

1
Gracias por esto. Me ahorró algunos accidentes y algunos rasguños en la cabeza :)
user2672052

58

Verificar currentDestinationantes de llamar a navegar puede ser útil.

Por ejemplo, si tiene dos destinos de fragmentos en el gráfico de navegación fragmentAy fragmentB, y solo hay una acción desde fragmentAhasta fragmentB. las llamadas navigate(R.id.action_fragmentA_to_fragmentB)resultarán IllegalArgumentExceptioncuando ya estabas activado fragmentB. Por lo tanto, siempre debe verificar currentDestinationantes de navegar.

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

3
Tengo una aplicación de búsqueda que navega con una acción con argumentos. Por lo tanto, podría navegar desde el CurrentDestination a sí mismo. Terminé haciendo lo mismo excepto navController.currentDestination == navController.graph.node. Sin embargo, se sintió un poco sucio y siento que no debería tener que hacer esto.
Shawn Maybush

84
La biblioteca no debería obligarnos a hacer esta verificación, de hecho es ridículo.
DaniloDeQueiroz

Tuve el mismo problema. Tenía un EditText y un botón 'guardar' para almacenar el contenido del EditText en la base de datos. Siempre se bloqueaba al presionar el botón 'guardar'. Sospecho que la razón tiene que ver con el hecho de que, para poder presionar el botón 'guardar', necesito deshacerme del teclado en pantalla tocando el botón Atrás.
The Fox

Esto busca una condición de error, pero no resuelve el problema. Curiosamente, esta condición es verdadera si la pila de navegación se vacía por razones no deseadas.
Mike76

1
incluso en iOS, aunque a veces se presionan múltiples ViewController cuando presiona el botón varias veces. Supongo que tanto Android como iOS tienen este problema.
coolcool1994

47

Puede verificar la acción solicitada en el destino actual del controlador de navegación.

ACTUALIZAR el uso agregado de acciones globales para una navegación segura.

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
Esta solución no funcionará para ninguna acción definida fuera de la currentDestinationlista de acciones de. Supongamos que tiene una acción global definida y use esa acción para navegar. Esto fallará porque la acción no está definida en la lista <action> de currentDestination. Agregar un cheque como currentDestination?.getAction(resId) != null || currentDestination?.id != resIddebería resolverlo, pero también podría no cubrir todos los casos.
wchristiansen

@wchristiansen, gracias por las notas. He actualizado el código con el uso de acciones globales
Alex Nuts el

@AlexNuts gran respuesta. Creo que puede eliminar ?: graph.getAction(resId)-> currentDestination?.getAction(resId)devolverá una acción para acciones globales o no globales (lo he probado). Además, sería mejor si se hizo uso de Args Safe -> en lugar de pasar navDirections: NavDirectionsde resIdy argspor separado.
Wess

@AlexNuts Tenga en cuenta que esta solución no admite la navegación al mismo destino que el destino actual. No es posible navegar desde el destino X con el Paquete Y al destino X con el Paquete Z.
Wess

18

También podría suceder si tiene un Fragmento A con un ViewPager de Fragmentos B e intenta navegar de B a C

Como en ViewPager los fragmentos no son un destino de A, su gráfico no sabría que está en B.

Una solución puede ser usar ADirections en B para navegar a C


En este caso, el bloqueo no ocurre siempre, sino que es raro. ¿Cómo resolverlo?
Srikar Reddy

Puede agregar una acción global dentro del navGraph y usarla para navegar
Abraham Mathew

1
Como B no debería necesitar conocer su padre exacto, sería mejor usar ADirections a través de una interfaz como (parentFragment as? XActionListener)?.Xaction()y tenga en cuenta que podría mantener esta función como una variable local si eso es útil
hmac

puede usted por favor compartir un código de ejemplo para ilustrar esto como lo he hecho mismo problema
Ikhiloya Imokhai

cualquiera podría tener un código de muestra, estoy atrapado en el mismo problema. Tener un fragmento y luego un fragmento de tabulación
Usman Zafer

13

Lo que hice para evitar el bloqueo es lo siguiente:

Tengo un BaseFragment, allí agregué esto funpara asegurarme de que destinationes conocido por currentDestination:

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

Vale la pena señalar que estoy usando el complemento SafeArgs .


12

En mi caso, estaba usando un botón de retroceso personalizado para navegar hacia arriba. Llamé onBackPressed()en lugar del siguiente código

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

Esto hizo IllegalArgumentExceptionque ocurriera. Después de cambiarlo para usar el navigateUp()método en su lugar, no volví a tener un bloqueo.


No entiendo cuál es la diferencia entre onBackPressed y esto, todavía atascado con el botón de retroceso del sistema y anularlo y reemplazarlo con esto parece una locura
Daniel Wilson

2
Estoy de acuerdo en que parece una locura. Muchas de las cosas que he encontrado en el componente de arquitectura de navegación de Android se sienten un poco locas, están configuradas de manera demasiado rígida en mi opinión. Pensando en hacer mi propia implementación para nuestro proyecto, ya que está creando demasiados dolores de cabeza
Neil

No funciona para mí ... Todavía obtengo el mismo error.
Otziii

5

TL; DR Envuelva sus navigatellamadas con try-catch(forma simple), o asegúrese de que solo haya una llamada navigateen un corto período de tiempo. Es probable que este problema no desaparezca. Copie un fragmento de código más grande en su aplicación y pruébelo.

Hola. Basado en un par de respuestas útiles anteriores, me gustaría compartir mi solución que se puede ampliar.

Aquí está el código que causó este bloqueo en mi aplicación:

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

Una forma de reproducir fácilmente el error es tocar con varios dedos en la lista de elementos donde el clic en cada elemento se resuelve en la navegación a la nueva pantalla (básicamente lo mismo que la gente notó: dos o más clics en un período de tiempo muy corto ) Me di cuenta que:

  1. La primera navigateinvocación siempre funciona bien;
  2. La segunda y todas las demás invocaciones del navigatemétodo se resuelven en IllegalArgumentException.

Desde mi punto de vista, esta situación puede aparecer con mucha frecuencia. Como repetir código es una mala práctica y siempre es bueno tener un punto de influencia, pensé en la siguiente solución:

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

}

Y así, el código anterior cambia solo en una línea desde esto:

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

a esto:

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

Incluso se hizo un poco más corto. El código se probó en el lugar exacto donde ocurrió el accidente. Ya no lo experimenté y usaré la misma solución para otras navegaciones para evitar el mismo error.

Cualquier idea es bienvenida!

¿Qué causa exactamente el accidente?

Recuerde que aquí trabajamos con el mismo gráfico de navegación, controlador de navegación y back-stack cuando usamos el método Navigation.findNavController.

Siempre obtenemos el mismo controlador y gráfico aquí. Cuando navigate(R.id.my_next_destination)se llama gráfico y el back-stack cambia casi instantáneamente mientras la interfaz de usuario aún no se actualiza. Simplemente no lo suficientemente rápido, pero eso está bien. Después de que el back-stack ha cambiado, el sistema de navegación recibe la segunda navigate(R.id.my_next_destination)llamada. Dado que el back-stack ha cambiado, ahora operamos en relación con el fragmento superior de la pila. El fragmento superior es el fragmento al que navega utilizando R.id.my_next_destination, pero no contiene más destinos con ID R.id.my_next_destination. Por lo tanto, se obtiene IllegalArgumentExceptiondebido a la identificación de la que el fragmento no sabe nada.

Este error exacto se puede encontrar en el NavController.javamétodo findDestination.


4

En mi caso, el problema ocurrió cuando había reutilizado uno de mis Fragmentos dentro de un viewpagerfragmento como hijo del viewpager. El viewpagerFragmento (que era el fragmento primario) se agregó en el xml de navegación, pero la acción no se agregó en el viewpagerfragmento primario.

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

Se solucionó el problema agregando la acción al fragmento del visor principal también como se muestra a continuación:

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

Hoy

def navigationVersion = "2.2.1"

El problema aún existe. Mi enfoque sobre Kotlin es:

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

Puede verificar antes de navegar si el Fragmento que solicita la navegación sigue siendo el destino actual, tomado de esta esencia .

Básicamente establece una etiqueta en el fragmento para una búsqueda posterior.

/**
 * 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 es solo una identificación que tendrá que agregar a su ids.xml, para asegurarse de que sea única. <item name="tag_navigation_destination_id" type="id" />

Más información sobre el error y la solución, y navigateSafe(...)métodos de extensión en "Arreglar el temido" ... es desconocido para este NavController "


He estudiado algunas soluciones diferentes a este problema, y ​​la suya es definitivamente la mejor. Me entristece ver tan poco amor por él
Lucas

1
puede ser útil crear un identificador único en lugar de NAV_DESTINATION_IDalgo como esto stackoverflow.com/a/15021758/1572848
William Reed

sí, actualicé la respuesta
Frank

¿De dónde viene la etiqueta y por qué es necesaria? Tengo problemas en los que los ID reales en el componente de navegación no coinciden con estos R.id.
riezebosch

R.id.tag_navigation_destination_ides solo una identificación que tendrá que agregar a su ids.xml, para asegurarse de que sea única. <item name="tag_navigation_destination_id" type="id" />
Frank

3

En mi caso, tenía varios archivos de gráficos de navegación e intentaba moverme de una ubicación de gráfico de navegación a un destino en otro gráfico de navegación.

Para esto tenemos que incluir el segundo gráfico de navegación en el primero como este

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

y agrega esto a tu acción:

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

donde second_graphesta:

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

en el segundo gráfico.

Más información aquí.


2

En mi caso, el error ocurrió porque tuve una acción de navegación con Single Topy las Clear Taskopciones habilitadas después de una pantalla de inicio.


1
Pero clearTask está en desuso, debe usar popUpTo () en su lugar.
Jerry Okafor

@ Po10cio Ninguna de esas banderas fue necesaria, simplemente la eliminé y se solucionó.
Eury Pérez Beltré

2

Recibí este mismo error porque usé un cajón de navegación y getSupportFragmentManager().beginTransaction().replace( )al mismo tiempo en algún lugar de mi código.

Me deshice del error usando esta condición (probando si el destino):

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

En mi caso, el error anterior se activó cuando estaba haciendo clic en las opciones del cajón de navegación. Básicamente, el código anterior ocultó el error, porque en mi código en algún lugar usé la navegación usando getSupportFragmentManager().beginTransaction().replace( )la condición:

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

nunca fue alcanzado porque (Navigation.findNavController(v).getCurrentDestination().getId()siempre estaba apuntando al fragmento de la casa. Solo debe usar Navigation.findNavController(v).navigate(R.id.your_action)o navegar por las funciones del controlador de gráficos para todas sus acciones de navegación.



1

Capté esta excepción después de algunos cambios de nombre de clases. Por ejemplo: tuve clases llamadas FragmentAcon @+is/fragment_aen el gráfico de navegación y FragmentBcon @+id/fragment_b. Luego borré FragmentAy cambié el nombre FragmentBa FragmentA. Entonces, después de que el nodo de FragmentAtodavía se quedó en el gráfico de navegación, y el nodo android:namede FragmentB'' cambió su nombre path.to.FragmentA. Tenía dos nodos con el mismo android:namey diferente android:id, y la acción que necesitaba se definió en el nodo de la clase eliminada.


1

Se me ocurre cuando presiono el botón Atrás dos veces. Al principio, intercepto KeyListenery anulo KeyEvent.KEYCODE_BACK. Agregué el siguiente código en la función nombrada OnResumepara el Fragmento, y luego esta pregunta / problema está resuelto.

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

Cuando me sucede por segunda vez, y su estado es el mismo que el primero, encuentro que tal vez use la adsurdfunción. Analicemos estas situaciones.

  1. En primer lugar, FragmentA navega a FragmentB, luego FragmentB navega a FragmentA, luego presiona el botón Atrás ... aparece el bloqueo.

  2. En segundo lugar, FragmentA navega a FragmentB, luego FragmentB navega a FragmentC, FragmentC navega a FragmentA, luego presiona el botón Atrás ... aparece el bloqueo.

Así que creo que al presionar el botón Atrás, FragmentA volverá a FragmentB o FragmentC, luego causa el desorden de inicio de sesión. Finalmente, encuentro que la función nombrada popBackStackse puede usar para retroceder en lugar de navegar.

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

Hasta ahora, el problema está realmente resuelto.


1

Parece que mezclar el control fragmentManager del backstack y el control de la arquitectura de navegación del backstack también puede causar este problema.

Por ejemplo, la muestra básica original de CameraX utilizaba la navegación backstack fragmentManager como se muestra a continuación y parece que no interactúa correctamente con Navigation:

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

Si registra el 'destino actual' con esta versión antes de pasar del fragmento principal (el fragmento de la cámara en este caso) y luego lo registra nuevamente cuando regrese al fragmento principal, puede ver desde la identificación en los registros que la identificación no es lo mismo. Supuestamente, la Navegación lo actualizó cuando se movió al fragmento y el fragmntManager no lo actualizó nuevamente al regresar. De los registros:

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

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

La versión actualizada de la muestra básica de CameraX usa la navegación para regresar así:

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

Esto funciona correctamente y los registros muestran la misma identificación cuando vuelven al fragmento principal.

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

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

Sospecho que la moraleja de la historia, al menos en este momento, es tener mucho cuidado al mezclar Navigation con fragmentManager navigation.


Esto suena plausible, investigaré más a fondo. ¿Alguien ha podido verificar o justificar este reclamo?
Jerry Okapara

@JerryOkafor: lo probé en una aplicación en la que estaba trabajando basado en la muestra de CameraX y lo verifiqué, pero sería bueno ver si alguien más ha visto esto también. De hecho, me perdí una 'navegación hacia atrás' en un lugar en la misma aplicación, así que también la arreglé nuevamente recientemente.
Mick

1

Una forma ridícula pero muy poderosa es: simplemente llame a esto:

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

Simplemente cree esta extensión:

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

1

Podría haber muchas razones para este problema. En mi caso, estaba usando el modelo MVVM y estaba observando un booleano para la navegación cuando el booleano es verdadero -> navegar de lo contrario no hace nada y esto funcionaba bien, pero hubo un error aquí

al presionar el botón Atrás desde el fragmento de destino, me encontraba con el mismo problema. Y el problema era el objeto booleano, ya que olvidé cambiar el valor booleano a falso, esto creó el desorden. Acabo de crear una función en viewModel para cambiar su valor a falso y lo llamó justo después de findNavController ()


1

Por lo general, cuando esto me sucede, tuve el problema descrito por Charles Madere: dos eventos de navegación activados en la misma interfaz de usuario, uno que cambia el CurrentDestination y el otro que falla porque el currentDestination ha cambiado. Esto puede suceder si toca dos veces o hace clic en dos vistas con un escucha de clics que llama a findNavController.navigate.

Por lo tanto, para resolver esto, puede usar if-cheques, try-catch o si está interesado, hay un findSafeNavController () que verifica esto por usted antes de navegar. También tiene un control de pelusa para asegurarse de que no se olvide de este problema.

GitHub

Artículo que detalla el problema


1

Si hace clic demasiado rápido, causará nulo y fallará.

Podemos usar RxBinding lib para ayudar en esto. Puede agregar aceleración y duración en el clic antes de que ocurra.

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

Estos artículos sobre la limitación en Android pueden ser útiles. ¡Salud!


1

Si está utilizando una vista de reciclaje simplemente agregue un enfriamiento de escucha de clics en su clic y también en el uso de su archivo xml de reciclaje android:splitMotionEvents="false"


1
Mira las respuestas debajo de la mía
Crazy

1

Después de pensar en el consejo de Ian Lake en este hilo de Twitter, se me ocurrió el siguiente enfoque. Habiendo NavControllerWrapperdefinido como tal:

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

Luego en el código de navegación:

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

1

He resuelto el mismo problema poniendo check antes de navegar en lugar de código repetitivo para hacer clic al instante

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

de acuerdo con esta respuesta

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


0

Esto me sucedió, mi problema era que estaba haciendo clic en un FAB tab item fragment. Estaba tratando de navegar desde uno de los fragmentos de elemento de pestaña a another fragment.

Pero de acuerdo con Ian Lago en esta respuesta tenemos que usar tablayouty viewpager, sin ayuda componente de navegación . Debido a esto, no hay una ruta de navegación desde tablayout que contenga un fragmento a un fragmento de elemento de pestaña.

ex:

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

La solución fue crear una ruta desde el diseño de pestaña que contiene el fragmento al fragmento deseado, por ejemplo: ruta: container fragment -> another fragment

Desventaja:

  • El gráfico de navegación ya no representa el flujo del usuario con precisión.

0

En mi caso, recibí ese error cuando intenté navegar desde otro hilo, en el 50% de los casos. Ejecutar el código en el hilo principal ayuda

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

Me gustaría ver más votos sobre esto, suena plausible pero no puedo verificarlo.
Jerry Okafor

0

En mi caso, esto ocurrió cuando accidentalmente agregué un +destino en acción, y el bloqueo solo ocurrió cuando fui al mismo fragmento varias veces.

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

La solución es eliminar +del destino de la acción, usar solo en @id/profileFragmentlugar de@+id/profileFragment

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

0

Solución actualizada @Alex Nuts

Si no hay acción para un fragmento en particular y desea navegar para fragmentar

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

Escribí estas extensiones

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

0

Creé esta función de extensión para 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)
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.