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

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

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

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

Я использую:

findNavContoller()

функция расширения фрагмента, чтобы получить доступ к navControler.

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

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

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

UPDATE добавлено использование глобальных действий для безопасной навигации.

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 8

Что я сделал, чтобы предотвратить аварию, это следующее:

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

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

Стоит отметить, что я использую плагин SafeArgs.

Ответ 9

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

Ответ 10

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

 if (findNavController().currentDestination?.id == R.id.currentFragment) {
    findNavController().navigate(R.id.action_current_next)}

Ответ 11

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, но он не содержит следующих дальнейших пунктов назначения с идентификатором R.id.my_next_destination. Таким образом, вы получаете IllegalArgumentException из-за идентификатора, о котором фрагмент ничего не знает.

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

Ответ 12

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

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

например:

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

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

Недостаток:

  • Навигационный график больше не представляет поток пользователя точно.

Ответ 13

Это происходит со мной, когда я нажимаю кнопку "Назад" два раза. Сначала я перехватываю 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([email protected]).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

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

Ответ 14

В моем случае проблема возникла, когда я повторно использовал один из своих фрагментов внутри фрагмента viewpager в качестве дочернего элемента viewpager. Фрагмент viewpager (который был родительским фрагментом) был добавлен в XML файл Navigation, но действие не было добавлено в родительский фрагмент 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>