Компонент навигации — Неверно начальный пункт назначения

#android #kotlin #android-fragments #android-architecture-navigation

Вопрос:

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

Я использую навигационный компонент для управления навигацией в приложении, и я начал замечать, что иногда, когда вы открываете backstack с помощью кнопки «Назад» на панели действий или кнопки «Назад устройства», он возвращается к фрагменту, который больше не является начальным пунктом назначения (или, по крайней мере, не должен быть).

В моем случае приложение запускается в MainFragment и после проверки подлинности переходит в DashboardFragment. Это довольно распространенный сценарий.

Навигация в приложении довольно плоская. большую часть времени он имеет глубину всего 1 уровень, поэтому почти все виды доступны с панели мониторинга.

Приложение запускается в режиме входа в систему, как и многие, а затем на панели мониторинга, где сеанс будет оставаться в качестве «начального пункта назначения». Для этого это делается в nav_graph с помощью popUpTo и popUpToInclusive.

 <fragment
    android:id="@ id/mainFragment"
    android:name="com.example.view.fragments.MainFragment"
    android:label="Welcome">
    <action
        android:id="@ id/action_mainFragment_to_dashboardFragment"
        app:destination="@id/dashboardFragment"
        app:popUpTo="@id/mainFragment"
        app:popUpToInclusive="true"/>
</fragment>

<fragment
    android:id="@ id/dashboardFragment"
    android:name="com.example.view.fragments.dashboard.DashboardFragment"
    android:label="@string/dashboard_header" >
    <action
        android:id="@ id/action_dashboardFragment_to_notificationsFragment"
        app:destination="@id/notificationsFragment" />
</fragment>
 

Когда пользователь успешно аутентифицируется и его время для перехода на панель мониторинга, я использую NavController.navigate (), чтобы отправить их туда.

 findNavController().navigate(
    MainFragmentDirections.actionMainFragmentToDashboardFragment()
)

// This should have the same result and it does appear to be affected by the same issue
// findNavController().navigate(R.id.action_mainFragment_to_dashboardFragment)
 

У меня есть панель действий со стрелкой назад и навигационным ящиком. В основной деятельности мне нужно определить конфигурацию AppBarConfiguration и переопределить onSupportNavigateUp()

 lateinit var appBarConfiguration: AppBarConfiguration
...
override fun onCreate(savedInstanceState: Bundle?) {
    Timber.d("onCreate()")
    super.onCreate(savedInstanceState)

    _binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    // There is 2 different drawer menu's respectfully.
    appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.mainFragment,
            R.id.dashboardFragment
        ), binding.drawerLayout
    )

    setSupportActionBar(binding.toolbar)
    setupActionBarWithNavController(navController, appBarConfiguration)
}
...
override fun onSupportNavigateUp(): Boolean {
    Timber.d("-> onSupportNavigateUp()")
    val breadcrumb = navController
        .backStack
        .map { it.destination }
        .filterNot { it is NavGraph }
        .joinToString(" > ") { it.displayName.split('/')[1] }

    Timber.d("Backstack: $breadcrumb")
    Timber.d("Previous backstack entry: ${navController.previousBackStackEntry?.destination?.displayName}")

    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
...
 

The logs look like this when we step back and it is working correctly

 D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: dashboardFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/dashboardFragment
D/DashboardFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/DashboardFragment: -> onViewCreated()
 

Я также заметил, что при использовании гамбургера в панели действий он также вызывает функцию onSupportNavigateUp()

 D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: pendingAction: false
D/MainActivity: Backstack: dashboardFragment
D/MainActivity: Previous backstack entry: null
 

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

 I/NavController: Ignoring popBackStack to destination com.example:id/mainFragment as it was not found on the current back stack
 

Теперь, когда он работает неправильно, вот как выглядят журналы

 D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: mainFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/mainFragment
D/MainFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/MainFragment: -> onViewCreated()
 

Это действительно похоже на то, что свойства popUpTo и popUpToInclusive не применяются (иногда) при выполнении навигации от основного фрагмента к фрагменту панели мониторинга. Также подозрительно, что, несмотря на то, что фрагмент панели мониторинга не установлен в качестве нового начального пункта назначения, он также отсутствует в фоновом режиме. Предполагая, что свойства НЕ были применены, я ожидал бы увидеть хлебную крошку Backstack: mainFragment > dashboardFragment > testingFragment

Любая помощь будет очень признательна!

Комментарии:

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

2. @ianhanniballake фрагмент приветствия приложения-это фрагмент входа в систему. Нет никакого другого представления (например, представление посадки/приветствия), которое распределяется между входом в систему и панелью мониторинга. Если пользователь правильно вошел в систему, он должен переместиться на панель мониторинга и быть установлен в качестве новой отправной точки. Это делается для того, чтобы, если приложение закрыто и его время ожидания истекает в течение времени ожидания приложения, мы пропускаем вход и возвращаемся на панель мониторинга. Добавление еще одного фрагмента в это сочетание не будет представлять представление и, таким образом, будет использоваться только для логики, а затем потребуется отступить от входа в систему или панели мониторинга, чтобы выйти из приложения.

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

4. @ianhanniballake основываясь на ваших утверждениях и применяя структуру моего приложения к примеру условной навигации, вы говорите, что моим начальным пунктом назначения будет фрагмент профиля. Это приложение нельзя использовать без аутентификации. представление входа в систему-это единственное представление, которое можно увидеть без проверки подлинности. Предполагая, что я установил панель мониторинга в качестве начального пункта назначения и условно перенаправил на вход в систему, мне затем нужно установить ее в качестве начального пункта назначения, чтобы изолировать пользователя только для этого представления. если они перемещаются вверх/назад, они возвращаются на панель мониторинга, для которой они ДОЛЖНЫ быть аутентифицированы.

5. Ваша панель мониторинга будет местом начала — фиксированным местом начала, на которое конкретно указывают Принципы навигации.

Ответ №1:

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

Его до сих пор неизвестно, почему popUpTo и popUpToInclusive ненадежны с помощью XML при навигации (даже с использованием NavDirections), однако до сих пор передача NavOptions во время навигации, похоже, решает проблему.

 findNavController().navigate(
    MainFragmentDirections.actionMainFragmentToDashboardFragment(),
    NavOptions.Builder().setPopUpTo(R.id.mainFragment, true).build()
)
 

До сих пор этот вопрос еще не возник снова.

Ответ №2:

Как сказано в первой ссылке Яна:

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

Ваш начальный пункт назначения — «дом» на навигационном графике, обозначенный значком «Маленький дом». Установите с startDestination помощью атрибута в XML. Когда библиотека навигации создает резервную копию, у нее всегда есть это место назначения внизу. И это всегда будет там, даже если вы попытаетесь избежать этого с poUpTo помощью атрибутов.

Вот почему, если есть фрагмент, который вы считаете своим «домашним», например, панель мониторинга, который должен явно принадлежать вам startDestination . Это последнее, что увидит пользователь, если он выйдет из вашего приложения. Если у вас есть что-то вроде входа в систему или экрана приветствия в качестве начального пункта назначения, они вернутся к этому.

Вот почему вам нужно установить фрагмент «домой» в качестве начального пункта назначения, а затем обрабатывать любую дополнительную навигацию на экране входа или приветствия оттуда. Просто так устроена навигационная система. Если вы попытаетесь обойти это (я это сделал!), вы столкнетесь с другими проблемами, и многие приятные функции, такие как автоматическое восстановление backstack, могут работать неправильно