#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, могут работать неправильно