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
, включенными после заставки.
Ответ 7
Похоже, вы очищаете задачу. Приложение может иметь одноразовую установку или серию экранов входа. Эти условные экраны не должны считаться начальным пунктом назначения вашего приложения.
https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional
Ответ 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);
}
Чтобы легко воспроизвести ошибку, нажмите несколькими пальцами на список элементов, где щелчок по каждому элементу разрешается при переходе на новый экран (в основном, как отмечали люди - два или более щелчка за очень короткий период времени).). Я заметил, что:
- Первый вызов
navigate
всегда работает нормально; - Второй и все другие вызовы метода
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
. Давайте проанализируем эти ситуации.
Сначала FragmentA переходит к FragmentB, затем FragmentB переходит к FragmentA, затем нажимает кнопку возврата... появляется сбой.
Во-вторых, 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>