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

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

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

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

Любая помощь будет принята с благодарностью.

Ответы (40)

У меня такая же ошибка, потому что я использовал панель навигации и 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) или функции контроллера навигационного графика для всех ваших действий навигации.

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

Например, если у вас есть два назначения фрагмента на навигационном графе fragmentA и fragmentB, и есть только одно действие от fragmentA до фрагментB. вызов 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)
}

Так же могло случиться, если у вас Фрагмент A с ViewPager фрагментов B И вы пытаетесь перейти от B к C

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

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

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

Hello. Based on a couple of useful responses above, I would like to share my solution that can be extended.

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

@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);
}
• 100001 период времени). Заметил, что:

  1. Первый переход вызов всегда работает нормально;
  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 method findDestination.

Это мне приходит в голову, когда я нажимаю кнопку назад два раза. Сначала я перехватываю 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
                        )

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

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

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


и добавьте это в свое действие:


, где второй_граф:


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

Подробнее здесь

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

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

// Обработка нажатия кнопки возврата
        view.findViewById  (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 использует навигацию для возврата следующим образом:

 // Обработка нажатия кнопки возврата
        view.findViewById  (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.

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

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

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

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

GitHub

Статья по проблеме

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

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

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

Эти статьи о троттлинге на Android могут помочь. Ура!

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

 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

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

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

/**
 * 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, чтобы убедиться, что он уникален.

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

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

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

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

In order to avoid this crash one of my colleagues wrote a small library which exposes a SafeNavController, a wrapper around the NavController and handles the cases when this crash occurs because of multiple navigate commands at the same time.

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

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

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

<действие
    android: id = "@ + id / action_foo"
    android: destination = "@ id / nested_graph" />

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

fun NavController.navigateSafe (направления: NavDirections) {
    // Получить действие по ID. Если действие не существует на текущем узле, вернитесь.
    val action = (currentDestination?: graph) .getAction (routes.actionId)?: return
    var destId = action.destinationId
    val dest = graph.findNode (destId)
    if (dest is NavGraph) {
        // Назначение действия - это вложенный граф, который не является реальным местом назначения.
        // Настоящий пункт назначения - это начальный пункт назначения этого графа, поэтому разрешите его.
        destId = dest.startDestination
    }
    if (currentDestination? .id! = destId) {
        навигация (направления)
    }
}

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

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

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

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

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

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

I was having this same issue in my projects, first I tried to debounce the clicks on the view that was triggering the navigation action, but after some experimenting I found that on really slow devices the debounce should be a very high value that causes the app to feel slow for users with fast devices.

Итак, я придумал следующие расширения для NavController, я думаю, что они соответствуют исходному API и просты в использовании:

fun NavController.safeNavigate(directions: NavDirections) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, null)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

fun NavController.safeNavigate(directions: NavDirections, navOptions: NavOptions?) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, navOptions)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

fun NavController.safeNavigate(directions: NavDirections, navigatorExtras: Navigator.Extras) {
    try {
        currentDestination?.getAction(directions.actionId) ?: return
        navigate(directions.actionId, directions.arguments, null, navigatorExtras)
    } catch (e : Exception) {
        logError("Navigation error", e)
    }
}

Обратите внимание, что я использую SafeArgs и NavDirections. Эти функции проверяют, допустимо ли действие из текущего пункта назначения, и осуществляют навигацию только в том случае, если действие не равно нулю. Часть try catch не требуется, если библиотека навигации каждый раз возвращает правильное действие, но я хотел исключить все возможные сбои.

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

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

• 100001 Однако предполагается, что это маловероятно.

Код:

fun NavController.navigateSafe (направления: NavDirections) {
    val navigateWillError = currentDestination? .getAction (routes.actionId) == null

    if (navigateWillError) {
        if (previousBackStackEntry? .destination? .getAction (routes.actionId)! = null) {
            // Это, вероятно, пользователь, который дважды быстро нажимает две разные кнопки или одну кнопку
            // Игнорировать ...
            возвращение
        }

        // Похоже на программную ошибку. Продолжайте и позвольте перемещаться по броску.
    }

    навигация (направления)
}

Попробуй

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

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

импорт androidx.fragment.app.Fragment
импортировать androidx.navigation.NavController
импортировать androidx.navigation.NavDirections
импортировать androidx.navigation.fragment.FragmentNavigator

fun Fragment.safeNavigateFromNavController (direction: NavDirections) {
    val navController = findNavController ()
    val destination = navController.currentDestination как FragmentNavigator.Destination
    if (javaClass.name == destination.className) {
        navController.navigate (направления)
    }
}

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

импорт androidx.fragment.app.Fragment
импортировать androidx.navigation.NavController
импортировать androidx.navigation.NavDirections
импортировать androidx.navigation.fragment.FragmentNavigator

встроенное развлечение  NavController.safeNavigate (direction: NavDirections) {
    val destination = this.currentDestination как FragmentNavigator.Destination
    if (T :: class.java.name == destination.className) {
        навигация (направления)
    }
}
  1. И используйте вот так из вашего фрагмента:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo ()
// новое использование
safeNavigateFromNavController (направление)

// старое использование
// findNavController (). safeNavigate  (действие)

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

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

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

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

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

Вот и все!

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

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

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

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

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

Я вызываю 2.3.1 Navigation и такая же ошибка возникает при изменении конфигурации приложения. Когда причина проблемы была обнаружена с помощью Debug, GaphId в NavHostFragment не вступил в силу, поскольку идентификатор, установленный в настоящее время при вызове navController.setGraph (). GraphId из NavHostFragment можно получить только из тега . В настоящее время эта проблема возникает, если в вашем коде динамически установлено несколько 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();
}

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

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

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

Edit 3/19/2019: Just to clarify a bit further, this crash is not exclusively reproducible by just "clicking the same view twice very very quickly". Alternatively, you can just use two fingers and click two (or more) views at the same time, where each view has their own navigation that they would perform. This is especially easy to do when you have a list of items. The above info on multiple click prevention will handle this case.

Редактировать 16.04.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
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обновлен раствор @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)
}

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

nav.xml
//reused fragment

    //issue got fixed when i added this action to the viewpager parent also
    

....
// viewpager parent fragment

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

nav.xml
//reused fragment

    //issue got fixed when i added this action to the viewpager parent also
    

....
// viewpager parent fragment

    

Обдумав совет Иэна Лейка в этой ветке твиттера, я пришел к следующему подходу. Имея 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)

Сегодня

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

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

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

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

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

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

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

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

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

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

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

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

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

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

например:

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

Решением было создать путь от макета вкладки, содержащей фрагмент, к намеченному фрагменту. пример: путь: фрагмент контейнера -> другой фрагмент

Недостаток:

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

Чтобы предотвратить сбой, я рекомендую использовать плагин 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-файле графа навигации.


        
    

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

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

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

2022 WebDevInsider