- Navigate Back with Navigation Component
- Or how to do that without creating a new instance of Fragment
- The problem
- Understanding
- Solution
- Update at the end
- Кнопка возврата назад в компоненте навигации Android
- 17 ответов
- Последнее обновление — 25 апреля 2019 г.
- Старые обновления
- Android navigation component. Простые вещи, которые приходится делать самому
- Переключение между пунктами меню
- Открытие новой Activity
- Передача параметров в startDestination
Navigate Back with Navigation Component
Or how to do that without creating a new instance of Fragment
Sep 27, 2019 · 4 min read
As we all know, Google introduced Navigation Components some time ago. And moreover there is already 2.1.0 version at the moment. But there is small problem: we can not go back to specific fragment without its recreation. So this small article will be about workaround, you can use to eliminate such flaw.
The problem
Suppose we have One Activity Several fragments n a vigation (by the way I wrote about such pattern previously, you can check that here). And suppose we have 3 fragments. Fragment one has its own state, which can be saved and restore, and fragment three contain a button to go back to the first one. You can check base project here(code) and here(xml).
To navigate from third fragment to first one we use navigation action with attribute popUpTo :
To understand the current problem we need to change state of first fragment to be different from default one, then go to the third fragment, and then press a button to navigate back to the first one and BAM, you loose you saved state and have default one again:
Moreover if we slow our open/close fragment animations you will see, that the animation of going back to first fragment is not really smooth:
Understanding
That happens because first fragment is getting deleted and recreate from scratch. Let’s go to source code of Navigation library to check that.
Actually all basic navigation work starts, when you call NavController.navigate . At this point 2 main steps happens (at least two main steps, that are important for us):
- Popup back stack until we reach fragment specified by popUpTo attribute
- Create new fragment, specified by destinationId attribute
Eventually we will come to FragmentNavigator.navigate method, which creates needed fragment inside. So actually without specifying popUpInclusive attribute we will have new instance of Fragment1 with old instance, lying in the back stack. With using this attribute, old fragment will be permanently deleted and replaced by new one.
Solution
Unfortunately I didn’t found any elegant solution:
- Y̶o̶u̶ ̶c̶a̶n̶ ̶n̶o̶t̶ ̶a̶v̶o̶i̶d̶ ̶w̶r̶i̶t̶i̶n̶g̶ ̶d̶e̶s̶t̶i̶n̶a̶t̶i̶o̶n̶I̶d̶,̶ ̶s̶i̶n̶c̶e̶ ̶t̶h̶i̶s̶ ̶a̶t̶t̶r̶i̶b̶u̶t̶e̶ ̶i̶s̶ ̶r̶e̶q̶u̶i̶r̶e̶d̶ . Actually this is not true. You can!! And this is actual right solution . Check the UPD in the end of article
- You can not use any other attributes
- You can not adjust FragmentNavigator for your needs
One thing you can do, that will help us, is to specify your own Navigator. So let’s do that.
First, describe you NavHostFragment:
Next, create your own Navigator, that will inherit fragment navigator. The main change here will be checking wether popUpTo is equal to destination . And if yes, that probably means (at least in our project) that you just want to go back to that fragment without recreation, so you can just return navDest without changing:
Don’t forget to place @Navigator.Name annotation on class with name fragment, so that your navigator will be used as FragmentNavigator instead of default one.
And the last thing override method of creating fragment navigator in NavHostFragment . And use that fragment instead of default one in your xml our whatever place.
Now lets check our flow with new awesome approach 😉
Update at the end
While I wrote that article, I’ve checked the source code of Navigation component one more time and realized, that I was completely blind 🙁
As appeared, you may not specify destination attribute, but specify popUpTo attribute. In that case NavController will know, that you want just to go back till fragment, specified in popUpTo attribute without creating anything else.
So the only one right solution in this problem, which I described at the beginning of the article, is:
So this whole article is becoming useless, but I just didn’t want to throw my little research away, considering the fact that I already wrote almost everything and I had no other material on that month. 🙁
Источник
Кнопка возврата назад в компоненте навигации Android
Я хотел бы знать, как правильно обрабатывать действия кнопки возврата системы с помощью контроллера навигации. В моем приложении у меня есть два фрагмента (например, фрагмент1 и фрагмент2), и у меня есть действие в фрагменте 1 с назначением для фрагмента2. Все работает хорошо, кроме одного — когда пользователь нажимает кнопку возврата системы во фрагменте 2, я хочу показать диалог (например, с помощью DialogFragment) для подтверждения выхода. Каков наилучший способ реализовать это поведение? Если я использую app:defaultNavHost=»true» в своем фрагменте хоста, он автоматически возвращается к игнорированию моих правил. И, кроме того, для чего этот компонент?
Должен ли я использовать «поп к» может быть?
17 ответов
Последнее обновление — 25 апреля 2019 г.
Новая версия androidx.activity ver. 1.0.0-alpha07 приносит некоторые изменения
Более подробные объяснения в официальном руководстве Android: Обеспечить пользовательскую обратную навигацию
Старые обновления
UPD: 3 апреля 2019 года
Теперь это упрощено. Подробнее здесь
Устаревший (с версии 1.0.0-alpha06 3 апреля 2019 г.):
Поскольку это, его можно реализовать, просто используя реализация JetPack OnBackPressedCallback в вашем фрагменте и добавьте его в активность: getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
Ваш фрагмент должен выглядеть так:
UPD : Ваша активность должна быть расширена AppCompatActivity или FragmentActivity и в файле Gradle:
Если вы используете BaseFragment для своего приложения, вы можете добавить onBackPressedDispatcher к вашему базовому фрагменту.
Переопределите onBackPressed () в вашем фрагменте, расширяя базовый фрагмент
Итак, я создал интерфейс
И реализовано это всеми фрагментами, которые нужно обработать кнопкой назад. В основной деятельности я переопределил метод onBackPressed() :
Итак, если верхний фрагмент моего хоста Navigation реализует интерфейс OnBackPressedListener , я вызываю его метод onBackPressed() , в другом месте я просто возвращаю стек назад и закрываю приложение, если задний стек пуст.
Вы можете предоставить свою собственную обратную навигацию, используя OnBackPressedDispatcher
Вот мое решение
Используйте androidx.appcompat.app.AppCompatActivity для действия, которое содержит фрагмент NavHostFragment .
Определите следующий интерфейс и внедрите его во все фрагменты назначения навигации
В вашей активности переопределите onSupportNavigateUp и onBackPressed :
Преимущество этого решения заключается в том, что фрагментам назначения навигации не нужно беспокоиться о том, что их слушатели не будут зарегистрированы, как только они отсоединятся.
В зависимости от вашей логики, если вы хотите закрыть только текущий фрагмент, вы должны передать viewLifecycleOwner, код показан ниже:
Однако, если вы хотите закрыть приложение на backPressed независимо от того, из какого фрагмента (вероятно, вы этого не захотите!), Не передавайте viewLifecycleOwner. Также, если вы хотите отключить кнопку возврата, не делайте ничего внутри handleOnBackPressed (), см. Ниже:
Если вы используете Navigation Component, следуйте приведенным ниже кодам в вашем методе onCreateView () (в этом примере я хочу просто закрыть свое приложение этим фрагментом)
Немного опоздал на вечеринку, но с последним выпуском Navigation Component 1.0.0-alpha09 теперь у нас есть AppBarConfiguration.OnNavigateUpListener.
Это 2 строки кода, которые можно прослушивать при нажатии на кнопку, от фрагментов [TESTED and WORKING]
рекомендуемый подход заключается в добавлении OnBackPressedCallback на OnBackPressedDispatcher действия.
Рекомендованный метод работал для меня, но после обновления моей реализации библиотеки ‘androidx.appcompat: appcompat: 1.1.0’
Реализуйте как ниже
Попробуй это. Я думаю, что это поможет вам.
Я попробовал решение Jurij Pitulja, но я просто не смог найти getOnBackPressedDispatcher или addOnBackPressedCallback, также используя решение Кирилла Ткача, не смог найти текущий фрагмент, так что вот мой:
Таким образом, вы можете во фрагменте решить, должно ли действие взять контроль над нажатой спиной или нет.
Кроме того, у вас есть BaseActivity для всех ваших действий, вы можете реализовать так
Если вы хотите обрабатывать обратную печать только в текущем фрагменте
Источник
Android navigation component. Простые вещи, которые приходится делать самому
Всем привет! Хочу рассказать об особенностях в работе Navigation Architecture Component, из-за которых у меня сложилось неоднозначное впечатление о библиотеке.
Эта статья не пошаговая инструкция, в ней опущены детали реализации, чтобы сосредоточить внимание на ключевых моментах. В интернете есть немало одинаковых примеров использования (есть и переводы) — они помогут познакомиться с библиотекой. Так же перед чтением предлагаю изучить документацию.
Сразу скажу, библиотеку безусловно считаю полезной и не исключаю возможности неверного использования, но, пожалуй, я перепробовал всё прежде чем писать эту статью.
Итак, вот сценарии, при реализации которых ожидания по функционалу не совпали с реальностью в реализации:
- переключение между пунктами меню в navigation drawer
- открытие новой Activity со своим графом навигации
- передача параметров в startDestination
Переключение между пунктами меню
Это одна из тех функций, которые повлияли на решение использовать Navigation Component.
Нужно всего лишь сделать одинаковыми id пунктов меню
и id экранов (destination в графе навигации)
затем нужно связать меню с контроллером навигации:
Навигация в меню заработала — ну разве не чудо?!
Обратите внимание на «гамбургер» (иконка меню), при переключении между пунктами меню он меняет своё состояние на кнопку «назад». Такое поведение показалось непривычным (привычное — как в приложении play market) и, какое-то время, я пытался разобраться, что же сделал не так?
Всё так! Перечитав документацию по принципам навигации (а именно: пункты два и три), понял, что «гамбургер» показывается только для startDestination, вернее так: кнопка «назад» показывается для всех, кроме startDestination. Ситуацию можно поменять применив различные уловки в подписке (addOnNavigatedListener()) на изменение destination, но их даже описывать не стоит. Работает так, нужно смириться.
Открытие новой Activity
Activity может выступать в качестве navigation host и, в то же время, в графе навигации может выступать в роли одного из destination. Открытие Activity без вложенного графа навигации работает как ожидается, то есть вызов:
осуществит переход (как в случае с фрагментами) и откроет запрошенную Activity.
Гораздо интереснее рассмотреть случай, когда целевая Activity сама выступает в роли navigation host, то есть вариант 2 из документации:
В качестве примера давайте рассмотрим Activity для добавления заметки. В ней будет основной фрагмент с полями ввода EditFragment, он в графе навигации будет startDestination. Давайте положим, что при редактировании нам нужно прикрепить фото, для этого будем переходить к PhotoFragment для получения снимка с камеры. Граф навигации будет выглядеть так:
EditActivity мало отличается от MainActivity. Основное отличие в том, что на EditActivity нет меню:
Activity открывается, навигация внутри неё работает:
Опять обратим внимание на кнопку навигации в toolbar — на стартовом EditFragment нет кнопки «Назад к parent Activity» (а хотелось бы). С точки зрения документации, тут всё законно: новый граф навигации, новое значение startDestination, на startDestination не показывается кнопка «Назад», конец.
Для тех, кому хочется вернуть привычное поведение c parent activity, сохранив при этом функционал переключения между фрагментами, могу предложить такой костыль подход:
Подписка нужна для того, чтобы для NavigationUI.ActionBarOnNavigatedListener все destination не являлись startDestination. Таким образом NavigationUI.ActionBarOnNavigatedListener не будет скрывать кнопку навигации (за деталями стоит обратиться к исходникам). Добавим к этому обработку onSupportNavigateUp() штатным образом на startDestination и получим то, что хотелось.
Стоит сказать, что решение это далеко от идеала хотя бы потому, что это неочевидное вмешательство в поведение библиотеки. Полагаю, могут возникнуть проблемы в случае использования deep links (ещё не проверял).
Передача параметров в startDestination
В Navigation Component есть механизм передачи параметров от одного destination другому. Есть даже инструмент для обеспечения безопасности типов за счёт кодогенерации (неплохо).
Сейчас мы разберём случай, из-за которого я не смог поставить твёрдую пятёрку этому функционалу.
Вернёмся к EditActivity, достаточно привычный сценарий, когда одна Activity используется для создания и редактирования объектов. При открытии объекта для редактирования в Activity нужно передать, например, id объекта — давайте сделаем это штатным образом:
Я добавил параметр непосредственно в корневой элемент графа (navigation), но можно добавить в целевой фрагмент. От этого изменится только способ получения параметра.
Я добавил add и edit action’s в одни из фрагментов, так они будут доступны только из него.
В этом примере ImportFragmentDirections — автоматически сгенерированый safe-args класс.
Вы, наверняка, обратили внимание на особенности получения параметров в EditFragment. Так работает, потому что edit action (из пункта 1) передаёт аргументы в EditActivity, а она, в свою очередь, почему-то жадничает не передаёт её в граф (например, вызовом navController.graph.setDefaultArguments()). Эту особенность можно обойти, если вручную подготовить navigation controller. Один из способов описан на StackOwerflow.
Пожалуй, наибольшая сложность возникнет при одновременном использовании в качестве startDestination и обычного destination. То есть, при переходе и передаче параметров в startDestination из любого другого destination этого графа, фрагменту придётся самостоятельно определять, откуда извлекать параметры: из arguments или из intent.extras. Это нужно иметь ввиду при проектировании переходов с передачей параметров.
Резюмируя, хочу отметить, что сам не перестал использовать библиотеку и, несмотря на перечисленные недостатки особенности, считаю её достаточно полезной, чтобы рекомендовать к использованию. Очень надеюсь, что в следующих релизах изменится ситуация по крайней мере с передачей параметров в startDestination.
Источник