NavController.OnDestinationChangedListener передает destination.id, не соответствующий инициированному идентификатору действия навигации

В моем Android-проекте у меня очень простой навигационный график, включающий два фрагмента: Master и Detail:

<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"
   app:startDestination="@id/wordsListFragment">

    <fragment
        android:id="@+id/wordsListFragment"
        android:name="com.***.presentation.view.WordsListFragment"
        android:label="List"
        tools:layout="@layout/words_list_fragment">
        <action
            android:id="@+id/action_wordsListFragment_to_wordDetailsFragment"
            app:destination="@id/wordDetailsFragment" />
    </fragment>
    <fragment
        android:id="@+id/wordDetailsFragment"
        android:name="com.***.presentation.view.WordDetailsFragment"
        android:label="Details"
        tools:layout="@layout/word_details_fragment" />
</navigation>

Сама навигация отлично работает в обоих направлениях, включая поведение «Назад». В этом проекте у меня есть одно действие, в котором я реализую OnDestinationChangedListener. Все это согласно следующей документации от Google: NavController Updating UI

Я вызываю следующий метод, когда пользователь щелкает элемент списка (находясь на главном фрагменте):

findNavController().navigate(R.id.action_wordsListFragment_to_wordDetailsFragment, null)

Тогда в родительском действии у меня есть следующая реализация:

private fun setupNavController() {
    navigationController = findNavController(R.id.nav_mainhost_fragment_container)
    navigationController.addOnDestinationChangedListener(mainDestinationChangedListener)
    appBarConfiguration = AppBarConfiguration(navigationController.graph)
    setupActionBarWithNavController(navigationController, appBarConfiguration)
}

и это объект слушателя:

private val mainDestinationChangedListener = 
NavController.OnDestinationChangedListener { controller, destination, arguments ->        

if (destination.id == R.id.action_wordsListFragment_to_wordDetailsFragment) {
        actionBar?.hide()
    } else {
        actionBar?.show()
    }
}

но destination.id не соответствует R.id.action_wordsListFragment_to_wordDetailsFragment

Я попытался очистить проект, очистить кеш IDE, кеш Gradle, но сгенерированные идентификаторы по-прежнему не совпадают. Я также пробовал использовать навигацию через безопасные аргументы:

val action = WordsListFragmentDirections.actionWordsListFragmentToWordDetailsFragment()
findNavController().navigate(action)

но результаты в данном слушателе всегда одинаковы (т. е. не совпадают).

Некоторые значения из отладки:

findNavController().navigate(1000021) //R.id.action_wordsListFragment_to_wordDetailsFragment

но следующий вызов в стеке имеет другое значение:  введите здесь описание изображения , что также соответствует значениям destination.id, переданным в OnDestinationChangedListener:

destination.id //2131231018

Любые намеки с вашей стороны более чем приветствуются. Я просто хочу распознать пункт назначения или идентификатор действия и соответствующим образом настроить ToolBar.


person gary0707    schedule 14.03.2020    source источник
comment
Вы нашли какое-нибудь решение? Я тоже получаю разные идентификаторы? Я не знаю как сравнивать   -  person musooff    schedule 04.06.2020
comment
К сожалению нет.   -  person gary0707    schedule 05.06.2020
comment
Я провел небольшое тестирование. Это как-то странно, но работает даже несмотря на то, что цифры разные. Во время отладки, если вы проверяете числа, они могут быть 1000021 и 2131231018, но проверка равенства продолжается. Я предполагаю, что, хотя значения int отличаются, поскольку они аннотированы с помощью @ResId, они могут иметь разные проверки равенства во время выполнения.   -  person musooff    schedule 05.06.2020
comment
@musooff на самом деле это не так, я проверяю равенство, но он все равно дает мне ложь, и я не могу делать то, что хотел бы   -  person FabioR    schedule 26.11.2020
comment
Я считаю, что идентификатором назначения в этом действии будет R.id.wordDetailsFragment. Попробуйте сравнить destination.id с идентификатором целевого фрагмента.   -  person lokesh    schedule 09.01.2021


Ответы (3)


Вы сравниваете fragmentId с actionId, поэтому всегда ложно.

здесь if (destination.id == R.id.action_wordsListFragment_to_wordDetailsFragment)

Поскольку destination.id - это fragmentId, а R.id.action_wordsListFragment_to_wordDetailsFragment - actionId

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

if (destination.id == R.id.wordDetailsFragment)

*Редактировать

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

val navController = findNavController(this,R.id.nav_host_fragment)// this maybe change
navController.addOnDestinationChangedListener { controller, destination, arguments ->
   if(destination.id == R.id.wordDetailsFragment) {
       actionBar?.hide()
   } else {
       actionBar?.show()
   }
}
person Mohammed Alaa    schedule 14.03.2020
comment
Боюсь, вы НЕ правы. Я уже проверял такой сценарий, но это не так. destination.id - 2131231021, а R.id.wordDetailsFragment -: 1000241 Я не впервые занимаюсь разработкой под Android. Я просто не понимаю, что (и как) меняет destination.id - person gary0707; 14.03.2020
comment
@ gary0707 Я обновил ответ, надеюсь, он вам поможет. - person Mohammed Alaa; 14.03.2020
comment
Спасибо за ваши усилия, но вы все еще не решаете проблему, которую я описываю выше. Мой слушатель вызвал: mainDestinationChangedListener уже добавлен, работает и запускается при необходимости. Механизм уведомления слушателя работает, но destination.id не соответствует ни идентификатору действия навигации, ни целевому (подробному) фрагменту. - person gary0707; 15.03.2020
comment
@ gary0707 вы устанавливаете mainDestinationChangedListener в navControler, например navController.addOnDestinationChangedListener(mainDestinationChangedListener) - person Mohammed Alaa; 15.03.2020
comment
Да, иначе как бы весь механизм работал, вызывая обратный вызов слушателя? К вашему сведению: я обновил свой вопрос кодом, который использую, так что вы, надеюсь, доверяете мне в этом отношении ;-) - person gary0707; 16.03.2020

Проблема связана с отладчиком студии Android. Если я печатаю значение в logcat или присваиваю его переменной, тогда значение будет 2XXXXXXXXX, но если я оцениваю с помощью отладчика, то значение будет 1XXXXXX.

Я проверил шестнадцатеричный код действия (R.id.xxxx) в APK. Когда я конвертирую шестнадцатеричный код в целое число, он дает значение в 2XXXXXXXXX, которое равно значению, напечатанному в logcat.

person Nanzbz    schedule 05.10.2020

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

navController.addOnDestinationChangedListener{ nc: NavController, nd: NavDestination, args: Bundle? ->

            val destinationClassName = (nc.currentDestination as FragmentNavigator.Destination).className

            when(destinationClassName) {

                "com.*domainName.*PackageName.*className" -> {
                    "Do you thing here"
                }

                else -> {
                    "Cater for the else block here"
                }
            }
        }
person ilatyphi95    schedule 27.07.2020
comment
Похоже, обходной путь - неправильное использование Android API. Приведение типов, использование жестко закодированных полных имен - правда? Я так не думаю. К вашему сведению: код, который я разместил в моем вопросе, работает в другом проекте, поэтому он должен быть sth. с конкретной конфигурацией / настройкой проекта. - person gary0707; 28.07.2020
comment
пробовали ли вы мое предложение, у меня была та же проблема, и после множества безуспешных поисков я решил изучить все возможности, предоставляемые приведенными аргументами, и обнаружил, что могу использовать обходной путь, который работает все время. Иногда вам нужен обходной путь, особенно если нет никакого решения. И полное имя класса жестко запрограммировано, чтобы решение было как можно более простым. - person ilatyphi95; 28.07.2020
comment
Нет, не пробовал. В производственном коде я не использую обходной путь, подобный предложенному вами. Как я уже упоминал, у меня была эта проблема в одном из проектов, в то время как в другом компонент навигации работает нормально. Я считаю, что мы должны использовать любой API для каждого дизайна, а не взламывать его. Эдскер Дейкстра рекомендует ... избегать хитрых уловок вроде чумы. - person gary0707; 28.07.2020