IllegalArgumentException: пункт назначения xxx неизвестен этому NavController

У меня проблема с новым компонентом архитектуры навигации Android, когда я пытаюсь перейти от одного фрагмента к другому, я получаю эту странную ошибку:

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

Любая другая навигация работает нормально, кроме этой.

Я использую findNavController() функцию фрагмента, чтобы получить доступ к NavController.

Любая помощь будет оценена по достоинству.


comment
Пожалуйста, предоставьте код для лучшего понимания.   -  person Alex    schedule 27.06.2018
comment
До сих пор частота появления этой ошибки была снижена с новыми выпусками библиотеки, но я думаю, что библиотека еще недостаточно хорошо документирована.   -  person Jerry Okafor    schedule 21.09.2018


Ответы (39)


В моем случае, если пользователь дважды очень быстро щелкнет одно и то же представление, произойдет сбой. Поэтому вам нужно реализовать какую-то логику, чтобы предотвратить множественные быстрые щелчки ... Что очень раздражает, но, похоже, необходимо.

Вы можете узнать больше о предотвращении этого здесь: Android, предотвращающий двойной щелчок по A Кнопка

Редактировать 19 марта 2019 г.: чтобы уточнить немного больше, этот сбой не может быть воспроизведен исключительно, просто «щелкнув один и тот же вид дважды очень и очень быстро». В качестве альтернативы вы можете просто использовать два пальца и одновременно щелкнуть два (или более) представления, при этом каждое представление имеет свою собственную навигацию, которую они будут выполнять. Это особенно легко сделать, когда у вас есть список элементов. Вышеупомянутая информация о предотвращении множественных кликов справится с этим случаем.

Редактировать 4/16/2020: на всякий случай, если вам не очень интересно читать это сообщение о переполнении стека выше, я включаю свое собственное (Kotlin) решение, которое я использовал для давно.

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
    }
}
person Charles Madere    schedule 20.09.2018
comment
Редактирование об использовании 2-х пальцев и одновременном нажатии 2-х представлений! Это ключ для меня и помог мне легко воспроизвести проблему. Отличное обновление с этой информацией. - person Richard Le Mesurier; 11.04.2019
comment
Во время фазы отладки я случайно щелкнул, когда приложение зависло в ожидании продолжения выполнения. Похоже на еще один случай двух последовательных нажатий на IDE - person marcolav; 22.08.2019
comment
Спасибо за это. Спас меня от нескольких вылетов и головной боли :) - person user2672052; 27.04.2020
comment
Это решение - хитрость, позволяющая обойти настоящую проблему: компонент навигации. Он также склонен к сбою на более медленных устройствах. Создание и наполнение нового фрагмента может занять более 200 мс. После этой задержки может быть отправлено второе событие щелчка, прежде чем фрагмент будет показан, и мы вернемся к той же проблеме. - person Nicolas; 17.08.2020
comment
На самом деле не работает в моем приложении. Я все еще могу щелкать достаточно быстро, чтобы вызвать сбой, по крайней мере, с отладочной сборкой. Похоже на проблему с безопасностью потоков, хотя на самом деле должен быть только один поток пользовательского интерфейса. Странный. - person The incredible Jan; 28.10.2020

Проверьте currentDestination перед вызовом функции навигации. Это может быть полезно.

Например, если у вас есть два назначения фрагмента на навигационном графе fragmentA и fragmentB, и есть только одно действие от fragmentA до fragmentB. вызов navigate(R.id.action_fragmentA_to_fragmentB) приведет к IllegalArgumentException, когда вы уже были на fragmentB. Поэтому вы всегда должны проверять currentDestination перед навигацией.

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
person theJian    schedule 12.12.2018
comment
У меня есть приложение для поиска, которое выполняет действие с аргументами. Таким образом, он мог переходить от текущего пункта назначения к самому себе. В итоге я сделал то же самое, за исключением navController.currentDestination == navController.graph.node. Хотя это было немного грязно, и я чувствую, что не должен этого делать. - person Shawn Maybush; 03.01.2019
comment
Библиотека не должна заставлять нас делать эту проверку, это действительно смешно. - person DaniloDeQueiroz; 05.04.2019
comment
Я была такая же проблема. У меня был EditText и кнопка «Сохранить» для хранения содержимого EditText в базе данных. Он всегда вылетал при нажатии кнопки «сохранить». Я подозреваю, что причина кроется в том, что для того, чтобы иметь возможность нажать кнопку «Сохранить», мне нужно избавиться от экранной клавиатуры, нажав кнопку «Назад». - person The Fox; 17.10.2019
comment
Это проверяет наличие ошибки, но не решает проблему. Интересно, что это условие выполняется, если задний стек навигации становится пустым по нежелательным причинам. - person Mike76; 25.11.2019
comment
Как передать аргументы ?? - person IgorGanapolsky; 28.05.2020
comment
даже в iOS, хотя иногда несколько ViewController нажимаются, когда вы нажимаете кнопку несколько раз. Думаю, эта проблема есть и у Android, и у iOS. - person coolcool1994; 12.06.2020
comment
Я делал навигацию на основе значений живых данных. Добавление этого устранило мою проблему. - person CanonicalBear; 17.11.2020
comment
В: Как получить правильный идентификатор фрагмента? A: Он доступен в nav_graph.xml в теге фрагмента: <fragment android:id="@+id/theIdOfTheFragment" .../> - person Ben Butterworth; 04.03.2021
comment
как я могу использовать это решение из фрагмента внутри пейджера просмотра, если у меня нет целевого идентификатора? - person Omar Beshary; 15.03.2021

Вы можете проверить запрошенное действие в текущем пункте назначения контроллера навигации.

В ОБНОВЛЕНИЕ добавлено использование глобальных действий для безопасной навигации.

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)
    }
}
person Alex Nuts    schedule 11.01.2019
comment
Это решение не будет работать для действий, определенных вне списка действий currentDestination. Допустим, у вас определено глобальное действие, и вы используете это действие для навигации. Это не удастся, потому что действие не определено в списке ‹action› текущего места назначения. Добавление проверки типа currentDestination?.getAction(resId) != null || currentDestination?.id != resId должно решить эту проблему, но также может не охватывать все случаи. - person wchristiansen; 02.10.2019
comment
@wchristiansen, спасибо за заметки. Я обновил код с использованием глобальных действий - person Alex Nuts; 15.10.2019
comment
@AlexNuts отличный ответ. Я думаю, вы можете удалить ?: graph.getAction(resId) - ›currentDestination?.getAction(resId) вернет действие как для глобальных, так и для неглобальных действий (я это тестировал). Кроме того, было бы лучше, если бы вы использовали Safe Args - ›скорее передавайте navDirections: NavDirections, чем resId и args по отдельности. - person Wess; 03.03.2020
comment
@AlexNuts Обратите внимание, что это решение не поддерживает переход к тому же месту назначения, что и текущий пункт назначения. I.o.w. переход от пункта назначения X с помощью Bundle Y к пункту назначения X с помощью Bundle Z невозможен. - person Wess; 03.03.2020
comment
Это не удастся, если действие перейдет к вложенному графу. В этом случае идентификатором назначения действия будет идентификатор вложенного графа, а не его начального назначения, минуя проверку. - person Nicolas; 17.08.2020
comment
Спасибо за идею! Но с моей точки зрения, гораздо проще обрабатывать исключения в оболочке navigateSafe. В итоге я получил следующее решение: vadzimv.dev/ 26.07.2021 / - person VadzimV; 27.07.2021

Для предотвращения сбоя я сделал следующее:

У меня есть BaseFragment, я добавил туда fun, чтобы убедиться, что destination известен currentDestination:

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

Стоит отметить, что я использую подключаемый модуль SafeArgs.

person Douglas Kazumi    schedule 19.06.2019
comment
Это должен быть принятый ответ. Принятый ответ не поддерживает переход к диалоговому окну - person Marek Teuchner; 18.02.2021

Это также может произойти, если у вас есть фрагмент A с ViewPager из фрагментов B, и вы пытаетесь перейти от B к C

Поскольку в ViewPager фрагменты не являются адресатом A, ваш график не будет знать, что вы находитесь на B.

Решением может быть использование ADirections в B для перехода к C

person AntPachon    schedule 16.04.2019
comment
В этом случае сбой происходит не каждый раз, а бывает редко. Как это решить? - person Srikar Reddy; 14.05.2019
comment
Вы можете добавить глобальное действие внутри navGraph и использовать его для навигации - person Abraham Mathew; 05.07.2019
comment
Поскольку B не должен знать своего точного родителя, было бы лучше использовать ADirections через интерфейс, например (parentFragment as? XActionListener)?.Xaction(), и обратите внимание, что вы можете сохранить эту функцию как локальную переменную, если это полезно. - person hmac; 17.09.2019
comment
не могли бы вы поделиться образцом кода, чтобы проиллюстрировать это, поскольку у меня такая же проблема - person Ikhiloya Imokhai; 11.12.2019
comment
кто-нибудь может, пожалуйста, образец кода, я застрял в той же проблеме. Есть фрагмент, а затем фрагмент табуляции - person Usman Zafer; 24.03.2020

В моем случае я использовал настраиваемую кнопку возврата для перехода вверх. Я вызвал onBackPressed() вместо следующего кода

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

Это привело к возникновению IllegalArgumentException. После того, как я изменил его на использование метода navigateUp(), у меня больше не было сбоев.

person the-ginger-geek    schedule 25.07.2018
comment
Я не понимаю, в чем разница между onBackPressed и этим, все еще застрял на кнопке возврата системы и переопределил ее и заменил это кажется сумасшедшим - person Daniel Wilson; 04.02.2019
comment
Я согласен, это кажется безумным. Многие вещи, с которыми я столкнулся в компоненте архитектуры навигации Android, кажутся немного сумасшедшими, они настроены слишком жестко, ИМО. Думаю о собственной реализации для нашего проекта, потому что это создает слишком много головной боли - person the-ginger-geek; 05.02.2019
comment
У меня не работает ... По-прежнему возникает та же ошибка. - person Otziii; 07.05.2019

TL; DR Оберните свои navigate вызовы try-catch (простой способ) или убедитесь, что будет только один вызов navigate за короткий период времени. Эта проблема, скорее всего, не исчезнет. Скопируйте более крупный фрагмент кода в свое приложение и попробуйте.

Привет. Основываясь на нескольких приведенных выше полезных ответах, я хотел бы поделиться своим решением, которое можно расширить.

Вот код, который вызвал этот сбой в моем приложении:

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

Способ легко воспроизвести ошибку - нажать несколькими пальцами на список элементов, где щелчок по каждому элементу разрешается при переходе к новому экрану (в основном так же, как отмечали люди - два или более щелчка за очень короткий период времени ). Я заметил, что:

  1. Первый вызов navigate всегда работает нормально;
  2. Второй и все другие вызовы метода navigate разрешаются в IllegalArgumentException.

С моей точки зрения, такая ситуация может возникать очень часто. Поскольку повторение кода - плохая практика, и всегда хорошо иметь одну точку влияния, я подумал о следующем решении:

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

}

Таким образом, приведенный выше код изменяется только в одной строке от этого:

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

к этому:

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

Он даже стал немного короче. Код был протестирован в том месте, где произошел сбой. Больше не испытывал этого, и будет использовать то же решение для других навигации, чтобы избежать той же ошибки в дальнейшем.

Любые мысли приветствуются!

Что именно вызывает сбой

Помните, что здесь мы работаем с одним и тем же графом навигации, контроллером навигации и бэк-стеком, когда используем метод Navigation.findNavController.

Здесь мы всегда получаем один и тот же контроллер и график. Когда navigate(R.id.my_next_destination) вызывается, график и обратный стек изменяется почти мгновенно, пока пользовательский интерфейс еще не обновлен. Просто недостаточно быстро, но это нормально. После смены бэк-стека навигационная система получает второй вызов navigate(R.id.my_next_destination). Поскольку задний стек изменился, теперь мы работаем относительно верхнего фрагмента в стеке. Верхний фрагмент - это фрагмент, к которому вы переходите с помощью R.id.my_next_destination, но он не содержит следующих пунктов назначения с ID R.id.my_next_destination. Таким образом, вы получаете IllegalArgumentException из-за идентификатора, о котором фрагмент ничего не знает.

Эту точную ошибку можно найти в NavController.java методе findDestination.

person Jenea Vranceanu    schedule 01.06.2019

Попробуй это

  1. Создайте эту функцию расширения (или обычную функцию):

ОБНОВЛЕНО (без отражения и более читабельно)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator

fun Fragment.safeNavigateFromNavController(directions: NavDirections) {
    val navController = findNavController()
    val destination = navController.currentDestination as FragmentNavigator.Destination
    if (javaClass.name == destination.className) {
        navController.navigate(directions)
    }
}

СТАРЫЙ (с отражением)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator

inline fun <reified T : Fragment> NavController.safeNavigate(directions: NavDirections) {
    val destination = this.currentDestination as FragmentNavigator.Destination
    if (T::class.java.name == destination.className) {
        navigate(directions)
    }
}
  1. И используйте это из своего фрагмента:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo()
// new usage
safeNavigateFromNavController(direction)

// old usage
// findNavController().safeNavigate<FragmentOne>(action)

Моя проблема была

У меня есть фрагмент (FragmentOne), который переходит к двум другим фрагментам (FragmentTwo и FragmentThree). На некоторых небольших устройствах пользователь нажимает кнопку, которая перенаправляет на FragmentTwo, но через несколько миллисекунд после того, как пользователь нажимает кнопку, которая перенаправляет на FragmentThree. Результат:

Неустранимое исключение: java.lang.IllegalArgumentException Действие навигации / пункт назначения action_fragmentOne_to_fragmentTwo не может быть найден из текущего пункта назначения Пункт назначения (fragmentThree) class = FragmentThree

Мое решение было:

Я проверяю, принадлежит ли текущий пункт назначения текущему фрагменту. Если это правда, я выполняю действие навигации.

Это все!

person Abner Escócio    schedule 29.01.2021
comment
Это отлично работает, спасибо! - person Alex Shevchyshen; 07.02.2021
comment
@AlexShevchyshen Выложил апдейт без рефлексии и более читабельный. - person Abner Escócio; 07.02.2021
comment
+1, но вы должны передать объект navDirections в этой функции расширения, а не в действии safeNavigateFromNavController (navDirection) - person Daniyal Javaid; 15.02.2021
comment
Этот ответ следует принять. - person Abhishek Singh; 04.06.2021

В моем случае проблема возникла, когда я повторно использовал один из моих фрагментов внутри фрагмента viewpager в качестве дочернего элемента viewpager. Фрагмент viewpager (который был родительским фрагментом) был добавлен в xml навигации, но действие не было добавлено в viewpager родительский фрагмент.

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

Исправлена ​​проблема, добавив действие к родительскому фрагменту окна просмотра, как показано ниже:

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>
person hushed_voice    schedule 12.10.2019

Сегодня

def navigationVersion = "2.2.1"

Проблема все еще существует. Мой подход к Котлину:

// 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)
    }
}
person Serg Burlaka    schedule 13.04.2020

В моем случае у меня было несколько файлов навигационного графа, и я пытался перейти из одного местоположения навигационного графа в место назначения в другом навигационном графе.

Для этого мы должны включить 2-й граф навигации в 1-й, как это

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

и добавьте это к своему действию:

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

где 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">

на втором графике.

Дополнительная информация здесь

person hushed_voice    schedule 10.12.2019

Вы можете проверить перед навигацией, является ли фрагмент, запрашивающий навигацию, по-прежнему текущим пунктом назначения, принят из этого суть.

По сути, он устанавливает тег на фрагменте для последующего поиска.

/**
 * 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 - это просто идентификатор, который вам нужно будет добавить в свой ids.xml, чтобы убедиться, что он уникален. <item name="tag_navigation_destination_id" type="id" />

Дополнительная информация об ошибке и решении, а также navigateSafe(...) методах расширения в Устранение ужасного «… неизвестно этому NavController»

person Frank    schedule 05.05.2020
comment
Я изучил несколько различных решений этой проблемы, и ваше решение определенно самое хорошее. Мне грустно видеть так мало любви к этому. - person Luke; 07.05.2020
comment
может быть полезно создать уникальный идентификатор вместо NAV_DESTINATION_ID с чем-то вроде этого stackoverflow.com/a/15021758/1572848 - person William Reed; 08.05.2020
comment
Откуда берется тег и зачем он нужен? У меня проблемы, когда фактические идентификаторы навигационного компонента не совпадают с R.id. - person riezebosch; 25.06.2020
comment
R.id.tag_navigation_destination_id - это просто идентификатор, который вам нужно будет добавить в свой ids.xml, чтобы убедиться, что он уникален. <item name="tag_navigation_destination_id" type="id" /> - person Frank; 25.06.2020
comment
Даже с этим решением возможно, что исходный сбой все еще происходит при извлечении backstack. Вы можете добавить fun Fragment.popBackStackSafe() { if (mayNavigate()) findNavController().popBackStack() } - person Luke; 21.09.2020

Я решил ту же проблему, поставив галочку перед навигацией вместо стандартного кода для мгновенного нажатия кнопки управления

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

согласно этому ответу

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

person mohammad    schedule 04.05.2020

В моем случае ошибка возникла из-за того, что у меня было действие навигации с включенными параметрами Single Top и Clear Task после экрана-заставки.

person Eury Pérez Beltré    schedule 28.06.2018
comment
Но clearTask устарел, вместо него следует использовать popUpTo (). - person Jerry Okafor; 21.09.2018
comment
@ Po10cio Ни один из этих флагов не понадобился, я просто удалил его, и он был исправлен. - person Eury Pérez Beltré; 24.09.2018

Я решаю эту проблему, проверяя, существует ли следующее действие в текущем пункте назначения.

public static void launchFragment(BaseFragment fragment, int action) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(action) != null) {       
        NavHostFragment.findNavController(fragment).navigate(action);
    }
}

public static void launchFragment(BaseFragment fragment, NavDirections directions) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(directions.getActionId()) != null) {       
        NavHostFragment.findNavController(fragment).navigate(directions);
    }
}

Это решает проблему, если пользователь быстро нажимает на две разные кнопки.

person Vodet    schedule 12.08.2020

Чтобы избежать этого сбоя, один из моих коллег написал небольшую библиотеку, которая предоставляет SafeNavController, оболочку для NavController и обрабатывает случаи, когда этот сбой происходит из-за нескольких команд навигации одновременно.

Вот короткий статья о проблеме в целом и ее решении.

Библиотеку можно найти здесь.

person Nagy Arthur    schedule 10.09.2020

Похоже, что смешивание элемента управления fragmentManager для backstack и элемента управления Navigation Architecture для backstack также может вызвать эту проблему.

Например, исходный базовый образец CameraX использовал backstack-навигацию fragmentManager, как показано ниже, и похоже, что он неправильно взаимодействовал с навигацией:

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

Если вы регистрируете «текущее место назначения» с помощью этой версии перед переходом от основного фрагмента (в данном случае фрагмент камеры), а затем снова регистрируете его, когда вы возвращаетесь к основному фрагменту, вы можете увидеть по идентификатору в журналах, что идентификатор это не то же самое. Предположительно, навигация обновляла его при переходе к фрагменту, а fragmntManager не обновлял его снова при возвращении. Из журналов:

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

После: D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

В обновленной версии базового примера CameraX для возврата используется навигация следующим образом:

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

Это работает правильно, и журналы показывают тот же идентификатор при возврате к основному фрагменту.

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

После: D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Я подозреваю, что мораль этой истории, по крайней мере, на данный момент, заключается в том, чтобы быть очень осторожным, смешивая навигацию с навигацией fragmentManager.

person Mick    schedule 31.12.2019
comment
Это звучит правдоподобно, я буду исследовать дальше. Кто-нибудь смог проверить или подтвердить это утверждение? - person Jerry Okafor; 06.05.2020
comment
@JerryOkafor - я протестировал его в приложении, над которым работал, на основе CameraX Sample, и проверил его, но было бы неплохо увидеть, видел ли это кто-то еще. На самом деле я пропустил «обратную навигацию» в одном месте в том же приложении, поэтому недавно исправил ее снова. - person Mick; 06.05.2020

Смешной, но очень мощный способ: просто назовите это:

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

Просто создайте это расширение:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}
person Amir Hossein Ghasemi    schedule 21.02.2020
comment
Спасибо за этот простой для понимания, но мощный пример. Не нужно возиться со слушателями кликов, и мы все равно можем использовать все API = D - person baskInEminence; 12.09.2020

Я написал это расширение

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}
person Oleksandr Yahnenko    schedule 16.03.2020
comment
холыш1т все работает. Сейчас тестируем - person Roger; 03.06.2021

У меня такая же ошибка, потому что я использовал панель навигации и getSupportFragmentManager().beginTransaction().replace( ) одновременно где-то в моем коде.

Я избавился от ошибки, используя это условие (проверка, есть ли пункт назначения):

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

В моем случае предыдущая ошибка была вызвана, когда я нажимал на параметры панели навигации. По сути, приведенный выше код скрыл ошибку, потому что где-то в моем коде я использовал навигацию, используя getSupportFragmentManager().beginTransaction().replace( ) Условие -

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

не был достигнут, потому что (Navigation.findNavController(v).getCurrentDestination().getId() всегда указывал на домашний фрагмент. Вы должны использовать только Navigation.findNavController(v).navigate(R.id.your_action) или функции контроллера навигационного графа для всех ваших действий навигации.

person Aness    schedule 31.01.2020

Как упоминалось в других ответах, это исключение обычно возникает, когда пользователь

  1. одновременно нажимает на несколько представлений, которые обрабатывают навигацию
  2. нажимает несколько раз на представление, которое обрабатывает навигацию.

Использование таймера для отключения щелчков не является подходящим способом решения этой проблемы. Если пользователь не был перемещен к месту назначения после истечения таймера, приложение все равно выйдет из строя, и во многих случаях, когда навигация не является действием, которое нужно выполнять, необходимы быстрые щелчки.

В случае 1 android:splitMotionEvents="false" в xml или setMotionEventSplittingEnabled(false) в исходном файле должны помочь. Установка этого атрибута в значение false позволит только одному представлению сделать щелчок. Вы можете прочитать об этом здесь

В случае 2 что-то задерживает процесс навигации, позволяя пользователю нажимать на представление несколько раз (вызовы API, анимация и т. Д.). По возможности следует устранить основную проблему, чтобы навигация происходила мгновенно, не позволяя пользователю дважды щелкнуть представление. Если задержка неизбежна, как в случае вызова API, отключение представления или отключение щелчка будет подходящим решением.

person Keyser Söze    schedule 25.08.2020

Похоже, вы решаете задачу. Приложение может иметь одноразовую настройку или серию экранов входа в систему. Эти условные экраны не следует рассматривать как начальную точку назначения вашего приложения.

https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional

person Patrick    schedule 29.06.2018

Я поймал это исключение после нескольких переименований классов. Например: у меня были классы с именем FragmentA с @+is/fragment_a в навигационном графике и FragmentB с @+id/fragment_b. Затем я удалил FragmentA и переименовал FragmentB в FragmentA. Таким образом, после этого узел FragmentA все еще оставался в навигационном графе, а узел android:name из FragmentB был переименован в path.to.FragmentA. У меня было два узла с одинаковыми android:name и разными android:id, и нужное мне действие было определено на узле удаленного класса.

person VasyaFromRussia    schedule 30.01.2019

Мне это приходит в голову, когда я нажимаю кнопку назад два раза. Сначала я перехватываю KeyListener и отменяю KeyEvent.KEYCODE_BACK. Я добавил приведенный ниже код в функцию с именем OnResume для фрагмента, и тогда этот вопрос / проблема решена.

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

Когда это случается со мной во второй раз, и его статус такой же, как и у первого, я обнаружил, что, возможно, использую функцию adsurd. Давайте проанализируем эти ситуации.

  1. Сначала FragmentA переходит к FragmentB, затем FragmentB переходит к FragmentA, затем нажимает кнопку возврата ... появляется сбой.

  2. Во-вторых, FragmentA переходит к FragmentB, затем FragmentB переходит к FragmentC, FragmentC переходит к FragmentA, затем нажимает кнопку возврата ... появляется сбой.

Поэтому я думаю, что при нажатии кнопки «Назад» FragmentA вернется к FragmentB или FragmentC, тогда это вызовет беспорядок при входе в систему. Наконец, я обнаружил, что функцию с именем popBackStack можно использовать для возврата, а не для навигации.

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

Пока проблема действительно решена.

person 唐德坤    schedule 29.08.2019

У этой проблемы может быть много причин. В моем случае я использовал модель MVVM, и я наблюдал логическое значение для навигации, когда логическое значение истинно -> навигация, еще ничего не делайте, и это работало нормально, но здесь была одна ошибка

при нажатии кнопки возврата из целевого фрагмента я столкнулся с той же проблемой. И проблема была в логическом объекте, поскольку я забыл изменить логическое значение на false, это создало беспорядок. Я просто создал функцию в viewModel, чтобы изменить ее значение на false и вызвал его сразу после findNavController ()

person Manoj Chouhan    schedule 13.03.2020

Обычно, когда это случается со мной, у меня возникает проблема, описанная Чарльзом Мадером: два события навигации запускаются в одном и том же пользовательском интерфейсе, одно меняет currentDestination, а другое терпит неудачу, потому что currentDestination изменяется. Это может произойти, если вы дважды коснетесь или щелкните два представления с помощью прослушивателя щелчков, вызывающего findNavController.navigate.

Итак, чтобы решить эту проблему, вы можете использовать if-check, try-catch или, если вам интересно, есть findSafeNavController (), который выполняет эту проверку перед переходом. Он также имеет функцию проверки на ворсинок, чтобы убедиться, что вы не забыли об этой проблеме.

GitHub

Статья с подробным описанием проблемы

person Gergely Hegedus    schedule 13.03.2020

Если вы нажмете слишком быстро, это приведет к обнулению и сбою.

Мы можем использовать RxBinding lib, чтобы помочь в этом. Вы можете добавить дроссель и продолжительность щелчка, прежде чем он произойдет.

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

Эти статьи о регулировании скорости в Android могут помочь. Ваше здоровье!

person Joshua    schedule 17.04.2020

Если вы используете recyclerview, просто добавьте перезарядку прослушивателя кликов при щелчке, а также в своем XML-файле recyclerview используйте android:splitMotionEvents="false"

person Crazy    schedule 18.04.2020
comment
Посмотри на ответы под моими - person Crazy; 26.04.2020

Обдумав совет Иэна Лейка в этой ветке твиттера, я придумал следующий подход. Если NavControllerWrapper определен как таковой:

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

Затем в коде навигации:

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

person azizbekian    schedule 20.03.2020
comment
В Kotlin есть функции расширения именно для этой цели, без оболочки. - person Nicolas; 17.08.2020
comment
Вы не можете выполнять модульное тестирование, когда используете функции расширения здесь и там. В качестве альтернативы, когда вы используете оболочку, вы вводите шов в компонент, поэтому вы можете имитировать компоненты в соответствии с вашими потребностями и выполнять чистое модульное тестирование. - person azizbekian; 17.08.2020

Обновите до ответа @AlexNuts для поддержки перехода к вложенному графу. Когда действие использует вложенный граф в качестве пункта назначения, например:

<action
    android:id="@+id/action_foo"
    android:destination="@id/nested_graph"/>

Идентификатор пункта назначения этого действия нельзя сравнивать с текущим пунктом назначения, поскольку текущий пункт назначения не может быть графиком. Должен быть разрешен начальный пункт назначения вложенного графа.

fun NavController.navigateSafe(directions: NavDirections) {
    // Get action by ID. If action doesn't exist on current node, return.
    val action = (currentDestination ?: graph).getAction(directions.actionId) ?: return
    var destId = action.destinationId
    val dest = graph.findNode(destId)
    if (dest is NavGraph) {
        // Action destination is a nested graph, which isn't a real destination.
        // The real destination is the start destination of that graph so resolve it.
        destId = dest.startDestination
    }
    if (currentDestination?.id != destId) {
        navigate(directions)
    }
}

Однако это предотвратит повторную навигацию к одному и тому же пункту назначения дважды, что иногда необходимо. Чтобы разрешить это, вы можете добавить проверку action.navOptions?.shouldLaunchSingleTop() и добавить app:launchSingleTop="true" к действиям, для которых вы не хотите дублировать назначения.

person Nicolas    schedule 16.08.2020

Я вызываю 2.3.1 Navigation, и такая же ошибка возникает при изменении конфигурации приложения. Когда причина проблемы была обнаружена с помощью отладки, GaphId в NavHostFragment не вступил в силу в качестве идентификатора, установленного в настоящее время при вызове navController.setGraph(). GraphId из NavHostFragment можно получить только из тега <androidx.fragment.app.FragmentContainerView/>. В настоящее время эта проблема возникает, если в вашем коде динамически установлено несколько GraphId. Когда интерфейс восстановлен, пункт назначения не может быть найден в кешированном GraphId. Вы можете решить эту проблему, вручную указав значение mGraphId в NavHostFragment через отражение при переключении Graph.

navController.setGraph(R.navigation.home_book_navigation);
try {
    Field graphIdField = hostFragment.getClass().getDeclaredField("mGraphId");
    graphIdField.setAccessible(true);
    graphIdField.set(navHostFragment, R.navigation.home_book_navigation);
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}
person wangke    schedule 29.11.2020

Еще одно решение той же проблемы с быстрым нажатием и навигацией:

fun NavController.doIfCurrentDestination(@IdRes destination: Int, action: NavController.()-> Unit){
    if(this.currentDestination?.id == destination){action()}
}

а затем используйте вот так:

findNavController().doIfCurrentDestination(R.id.my_destination){ navigate(...) }

Преимущества этого решения заключаются в том, что вы можете легко обернуть любой существующий вызов naviagte() любой подписью, которую вы уже используете, без необходимости делать миллион перегрузок.

person Mardann    schedule 15.12.2020

Это случилось со мной, моя проблема заключалась в том, что я нажимал FAB на tab item fragment. Я пытался перейти от одного из фрагментов вкладки к another fragment.

Но согласно Яну Лэйку в этот ответ мы должны использовать tablayout и viewpager, без поддержки компонентов навигации. Из-за этого не существует пути навигации от макета вкладки, содержащего фрагмент, к фрагменту элемента вкладки.

ex:

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

Решение заключалось в создании пути из макета вкладки, содержащего фрагмент, к намеченному фрагменту, например: путь: container fragment -> another fragment

Недостаток:

  • График навигации больше не отображает точно пользовательский поток.
person user158    schedule 02.07.2019

Обновленное решение @Alex Nuts

Если для определенного фрагмента нет действий и вы хотите перейти к фрагменту

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)
}
person Sumit    schedule 15.01.2020

Я создал эту функцию расширения для фрагмента:

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)
        }
    }
}
person Mehmed    schedule 01.04.2020

Чтобы предотвратить сбой, я рекомендую использовать плагин safeargs, в противном случае вам нужно проверять каждый раз, существует идентификатор места назначения или нет. Другая вещь находится в вашем файле меню для нижней навигации, каждый идентификатор элемента должен быть таким же, как идентификатор фрагмента, который добавлен в файл xml графа навигации.

Если вы используете kotlin, вам нужно добавить некоторые зависимости в свой build.gradle.

android{
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
}

Для SafeArgs

dependencies {
classpath  'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
}

Чтобы перейти к месту назначения, сначала вы должны соединить их через действие в xml-файле графа навигации.

<fragment
        android:id="@+id/employeesListFragment"
        android:name="com.example.navigationjetpacksample.fragments.EmployeesListFragment"
        android:label="EmployeesListFragment"
        tools:layout="@layout/frag_employees_list">
        <action
            android:id="@+id/action_employeesListFragment_to_addEmployeeFragment"
            app:destination="@id/addEmployeeFragment" />
    </fragment>

После сборки / очистки вашего проекта,

 fab_add.setOnClickListener {   it.findNavController().navigate(EmployeesListFragmentDirections.actionEmployeesListFragmentToAddEmployeeFragment())
 }

Для получения дополнительной информации: Пример навигации Jetpack

person Dhrumil Shah    schedule 07.06.2020

Ответили по ссылке: https://stackoverflow.com/a/67614469/5151336

Добавлена ​​функция расширения для навигатора для безопасной навигации.

person vishnu benny    schedule 21.05.2021

Бросаю свой ответ в кольцо, которое обрабатывает два случая (двойное нажатие, одновременное нажатие на две кнопки) элегантно, но пытается не замаскировать настоящие ошибки.

Мы можем использовать функцию navigateSafe(), которая проверяет, является ли пункт назначения, к которому мы пытаемся перейти, недопустимым для текущего пункта назначения, но действителен ли он для предыдущего пункта назначения. В этом случае код предполагает, что пользователь либо дважды нажал, либо нажал две кнопки одновременно.

Это решение, однако, не идеально, так как оно может маскировать реальные проблемы в нишевых случаях, когда мы пытаемся перейти к чему-то, что случайно является местом назначения родителя. Однако предполагается, что это маловероятно.

Код:

fun NavController.navigateSafe(directions: NavDirections) {
    val navigateWillError = currentDestination?.getAction(directions.actionId) == null

    if (navigateWillError) {
        if (previousBackStackEntry?.destination?.getAction(directions.actionId) != null) {
            // This is probably some user tapping two different buttons or one button twice quickly
            // Ignore...
            return
        }

        // This seems like a programming error. Proceed and let navigate throw.
    }

    navigate(directions)
}
person idunnololz    schedule 04.07.2021

Эта ошибка могла возникнуть из-за того, что вы, возможно, назначили целевой экран неправильному графику.

person Max Zonov    schedule 02.06.2020
comment
Каково решение? - person IgorGanapolsky; 12.07.2020
comment
@IgorGanapolsky просто замените свой navigation экран на правый график - person Max Zonov; 21.07.2020