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


139

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

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

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

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

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


Пожалуйста, предоставьте код для лучшего понимания.
Alex

12
Это тоже происходит со мной.
Eury Pérez Beltré

Пока что частота появления этой ошибки была снижена с более новыми выпусками библиотеки, но я думаю, что библиотека еще не хорошо документирована.
Джерри Окафор

Ответы:


76

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

Вы можете узнать больше о предотвращении этого здесь: Android Preventing Double Click On A Button

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

23
Редактирование об использовании 2-х пальцев и одновременном нажатии 2-х представлений! Это ключ для меня и помог мне легко воспроизвести проблему. Отличное обновление с этой информацией.
Ричард Ле

Во время фазы отладки я случайно щелкнул, пока приложение зависло в ожидании продолжения выполнения. Похоже, еще один случай двух последовательных нажатий на IDE
Марко

1
Спасибо за это. Спас меня от нескольких
вылетов

58

Проверка 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)
}

3
У меня есть приложение для поиска, которое выполняет действие с аргументами. Таким образом, он может перейти от текущего пункта назначения к самому себе. В итоге я сделал то же самое, за исключением navController.currentDestination == navController.graph.node. Хотя это было немного грязно, и я чувствую, что не должен этого делать.
Шон Мэйбуш

87
Библиотека не должна заставлять нас делать эту проверку, это действительно смешно.
DaniloDeQueiroz

Я была такая же проблема. У меня был EditText и кнопка «сохранить», чтобы сохранить содержимое EditText в базе данных. Всегда вылетал при нажатии кнопки «сохранить». Я подозреваю, что причина связана с тем, что для того, чтобы иметь возможность нажать кнопку «сохранить», мне нужно избавиться от экранной клавиатуры, нажав кнопку «Назад».
The Fox

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

1
даже в iOS, хотя иногда несколько ViewController нажимаются, когда вы нажимаете кнопку несколько раз. Думаю, эта проблема есть у Android и iOS.
coolcool1994

47

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

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

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}

1
Это решение не будет работать ни для каких действий, определенных вне currentDestinationсписка действий. Допустим, у вас определено глобальное действие, и вы используете это действие для навигации. Это не удастся, потому что действие не определено в списке <action> currentDestination. Добавление проверки currentDestination?.getAction(resId) != null || currentDestination?.id != resIdдолжно решить эту проблему, но также не может охватить все случаи.
wchristiansen

@wchristiansen, спасибо за заметки. Я обновил код с использованием глобальных действий
Alex Nuts

@AlexNuts отличный ответ. Я думаю, вы можете удалить ?: graph.getAction(resId)-> currentDestination?.getAction(resId)вернет действие как для глобальных, так и для неглобальных действий (я это тестировал). Кроме того, было бы лучше, если бы вы использовали Safe Args -> скорее переходите, navDirections: NavDirectionsчем resIdи argsотдельно.
Wess,

@AlexNuts Обратите внимание, что это решение не поддерживает переход к тому же месту назначения, что и текущий пункт назначения. В настоящее время навигация от пункта назначения X с помощью Bundle Y к пункту назначения X с помощью Bundle Z невозможна.
Wess,

18

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

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

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


В этом случае сбой происходит не каждый раз, а бывает редко. Как это решить?
Srikar Reddy

Вы можете добавить глобальное действие внутри navGraph и использовать его для навигации
Авраам Мэтью

1
Поскольку B не должен знать своего точного родителя, было бы лучше использовать ADirections через интерфейс, например, (parentFragment as? XActionListener)?.Xaction()и обратите внимание, что вы могли бы сохранить эту функцию как локальную переменную, если это полезно
hmac

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

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

13

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

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

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

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


12

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

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

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


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

2
Я согласен, это кажется безумным. Многие вещи, с которыми я столкнулся в компоненте архитектуры навигации Android, кажутся немного сумасшедшими, они слишком жестко настроены IMO. Подумываю о своей собственной реализации для нашего проекта, потому что это создает слишком много головной боли
Нил

У меня не работает ... По-прежнему возникает та же ошибка.
Otziii

6

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.


4

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

4

Cегодня

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

4

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

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

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


Я изучил несколько различных решений этой проблемы, и ваше решение определенно самое хорошее. Мне грустно видеть так мало любви к нему
Лука

1
может быть полезно создать уникальный идентификатор вместо NAV_DESTINATION_IDчего-то вроде этого stackoverflow.com/a/15021758/1572848
Уильям Рид

да, я обновил ответ
Фрэнк

Откуда метка и зачем она нужна? У меня проблемы, когда фактические идентификаторы навигационного компонента не совпадают с ними R.id.
riezebosch,

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

3

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

Для этого мы должны включить 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">

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

Больше информации здесь


3

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

 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


2

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


1
Но clearTask устарел, вместо него следует использовать popUpTo ().
Джерри Окафор

@ Po10cio Ни один из этих флагов не понадобился, я просто удалил его, и он был исправлен.
Eury Pérez Beltré

2

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


1

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

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


1

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


1

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

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


1

Кажется, что смешивание элемента управления fragmentManager backstack и элемента управления архитектурой навигации 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.


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

@JerryOkafor - я протестировал его в приложении, над которым работал, на основе CameraX Sample и проверил его, но было бы неплохо увидеть, видел ли это кто-то еще. На самом деле я пропустил «обратную навигацию» в одном месте в том же приложении, поэтому недавно исправил ее снова.
Мик

1

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

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

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

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

1

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

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


1

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

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

GitHub

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


1

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

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

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

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


1

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


1
Посмотрите на ответы под моими
Crazy

1

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

1

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

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

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


0

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

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

например:

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

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

Недостаток:

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

0

В моем случае я получал эту ошибку при попытке перейти из другого потока в 50% случаев. Запустить код в основном потоке помогает

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

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

0

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

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

Решение - удалить +из пункта назначения действия, использовать только @id/profileFragmentвместо@+id/profileFragment

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

0

Обновленное решение @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)
}

0

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

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.