- 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
- Краткое руководство по созданию навигации на основе фрагментов
- Мир Activity
- Мир фрагментов
- Полноэкранные фрагменты в роли ночного кошмара
- Методика создания навигации на основе фрагментов
- Преимущества:
- 1. Файл AndroidManifest.xml получается более чистым и удобным в сопровождении
- 2. Централизованное управление навигацией
- 3. Улучшение взаимодействия между экранами
- 4. Создание фрагментом обходится дешевле создания активностей
- Недостатки
- Заключение
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-приложениях. Кто-то использует только активности (Activity), кто-то смешивает их с фрагментами (Fragment) и/или с модифицированными видами (Custom View).
Один из способов реализации навигации в приложении основывается на философии «Одна активность — много фрагментов». При таком подходе каждый экран приложения состоит из многочисленных фрагментов, при этом все или большинство из них содержат по одной активности. Такое решение упрощает реализацию навигации и улучшает производительность, что положительно сказывается на впечатлении от использования приложения.
В этой статье мы рассмотрим несколько характерных подходов к реализации навигации в Android, а затем поговорим о подходе с использованием фрагментов, сравнив его с другими подходами. Демонстрационное приложение, на примере которого иллюстрирована статья, можно скачать с GitHub.
Мир Activity
Типичное Android-приложение, использующее активность, имеет древовидную структуру (точнее, структуру ориентированного графа), при которой активность, инициализируемая при запуске приложения, является «корнем». В ходе использования приложения операционная система поддерживает работу стека переходов назад (back stack). Простой пример:
Активность A1 является входной точкой приложения (это может быть экран-заставка или основное меню), из которой пользователь может перейти к A2 или A3. Для организации взаимодействия между активностями можно использовать startActivityForResult() , либо сделать общую, глобально доступную бизнес-логику.
Если вы хотите добавить новую активность, то нужно выполнить следующие шаги:
- Задать саму активность.
- Зарегистрировать её в AndroidManifest.xml.
- Открыть её из другой активности с помощью startActivity().
Конечно, вышеприведённая схема описывает весьма упрощённый подход. Всё может оказаться куда сложнее, если вам понадобится манипулировать стеком переходов назад. Или если вы захотите несколько раз использовать одну и ту же активность: например, пользователь будет переходить между разными обучающими экранами, в основе каждого из которых будет лежать одна и та же активность.
К счастью, у нас есть такие инструменты, как задачи и гайдлайны по правильной работе со стеком переходов назад. Однако с появлением API level 11 были внедрены фрагменты…
Мир фрагментов
Как утверждается в официальной документации от Google, «фрагменты были представлены в Android 3.0 (API level 11) в первую очередь для создания более динамичных и гибких интерфейсов, адаптированных для больших экранов, например, планшетов. У них площадь экранов гораздо больше, чем у смартфонов, соответственно больше места для комбинирования и перестановки элементов интерфейса. Благодаря фрагментам вам не придётся управлять сложными изменениями в иерархии видов при создании подобных интерфейсов. Разделяя структуру активности на фрагменты, вы получаете возможность изменять её отображение в ходе выполнения программы, защищая регулируемые активностью изменения в стеке переходов назад».
Этот новый инструмент позволил разработчикам создавать интерфейсы, состоящие из нескольких отдельных секций, а также повторно использовать компоненты в других активностях. Кому-то это нравится, кому-то нет. Сегодня много спорят о том, нужно ли использовать фрагменты, но, вероятно, все согласятся с тем, что с их появлением выросла сложность разработки и приложений, поэтому разработчики должны хорошо разбираться в том, как правильно применять фрагменты.
Полноэкранные фрагменты в роли ночного кошмара
Всё чаще можно было встретить приложения, в которых не часть экрана состоит из нескольких фрагментов, а весь экран представляет собой один фрагмент, помещённый в активность. Можно даже наткнуться на поделки, в которых каждая активность содержит по одному полноэкранному фрагменту, и ничего более. То есть активности выступают исключительно в роли контейнера для этих фрагментов. У такого подхода, помимо очевидной ошибки проектирования, есть и другая проблема. Взгляните на схему:
Как А1 будет взаимодействовать с F1? Да, А1 полностью контролирует F1, ведь это она его создала. Например, А1 могла бы во время создания передать F1 какой-нибудь бандл (bundle), или вызвать его открытые методы.
А как F1 будет взаимодействовать с А1? Здесь уже несколько сложнее, но можно выйти из положения с помощью методики callback/observer, при которой А1 подписывается на F1, а тот уведомляет своего подписчика.
Ладно, а что насчёт взаимодействия между А1 и А2? Эта задача решается, например, с помощью startActivityForResult() .
А теперь вопрос: как будут взаимодействовать друг с другом F1 и F2? Даже в этом случае у нас может быть общедоступный компонент, содержащий бизнес-логику, который и можно использовать для передачи данных. Но это не всегда помогает создать красивый дизайн. Допустим, F2 должен передать F1 какие-то данные. При использовании callback-методики F2 может уведомить А2, тот генерирует какой-то результат, А1 его получает и уведомляет F1.
При таком подходе придётся использовать много шаблонного кода, который быстро станет источником багов, боли и раздражения. А что если нам избавиться от всех этих активностей, оставив лишь одну из них, поместив в неё остальные фрагменты?
Методика создания навигации на основе фрагментов
Подход, который будет описан, вызывает много споров (пример 1, пример 2). Давайте рассмотрим конкретный пример.
Теперь у нас осталась единственная активность, выполняющая роль контейнера. Она содержит несколько фрагментов, составляющих древовидную структуру. Навигация между ними осуществляется с помощью FragmentManager , имеющего собственный стек переходов назад. Обратите внимание, что здесь отсутствует startActivityForResult() , но мы всё же можем реализовать методику callback/observer. Давайте разберём преимущества и недостатки этого подхода.
Преимущества:
1. Файл AndroidManifest.xml получается более чистым и удобным в сопровождении
Поскольку осталась лишь одна активность, нам больше не нужно обновлять манифест при каждом добавлении нового экрана. Фрагменты объявлять не нужно, в отличие от активностей. На первый взгляд, это мелочь, но если приложение большое и содержит несколько десятков активностей, то файл манифеста станет гораздо читабельнее.
Взгляните на манифест нашего приложения-примера, содержащего несколько экранов. У него очень простое содержимое:
2. Централизованное управление навигацией
Просмотрев код приложения, вы можете заметить, что в нём отсутствует NavigationManager . В нашем случае он внедрён в каждый фрагмент. Этот диспетчер можно использовать в качестве центра для сбора логов, управления стеком переходов назад и т.д. Поэтому схемы навигации можно отделить от бизнес-логики и не распределять по реализациям разных экранов.
Представим, что нам нужно отобразить экран, на котором пользователь может выбрать несколько человек из списка. Возможно, понадобится использовать фильтры вроде возраста, места жительства или пола.
При использовании нескольких активностей пришлось бы написать:
Далее нужно задать onActivityResult и обработать результат:
Проблема в том, что все эти аргументы — «лишние» и не обязательные. Поэтому нужно удостовериться, что активность обрабатывает все возможные случаи, когда фильтры не используются. Позднее, когда будет сделан какой-то рефакторинг и один из фильтров окажется больше не нужен, придётся найти все места в коде, откуда запускается эта активность, и удостовериться, что все фильтры работают правильно.
Кроме того, лучше сделать так, чтобы результат (список людей) приходил в виде _List_, а не в сериализованном виде, который нужно будет десериализовать.
Если же мы используем навигацию на основе фрагментов, то всё становится проще. Достаточно написать в NavigationManager метод startPersonSelectorFragment() со всеми необходимыми аргументами реализацией callback’ов.
3. Улучшение взаимодействия между экранами
Между активностями могут передаваться только бандлы, содержащие примитивы или сериализованные данные. А благодаря фрагментам есть возможность реализовать callback-методику, когда F1, к примеру, может получать от F2 произвольные объекты. Посмотрите предыдущие примеры реализации callback’ов, в которых возвращается _List_.
4. Создание фрагментом обходится дешевле создания активностей
Это становится очевидным при использовании навигационной панели (drawer), содержащей несколько элементов меню, которая должна отображаться на каждой странице.
Если ваша навигация построена на одних лишь активностях, то каждая страница должна «раздуть» (inflate) и инициализировать панель, что потребляет немало ресурсов.
На следующей схеме представлено несколько корневых фрагментов (FR*), являющихся полноэкранными. Получить к ним доступ можно напрямую из панели, а к ней самой можно обратиться лишь тогда, когда отображается один из корневых фрагментов. Часть схемы по правую сторону от пунктирной линии — пример произвольной схемы навигации.
Поскольку панель находится внутри контейнера, то у нас есть лишь один её экземпляр. Поэтому панель должна быть видимой на каждом этапе навигации, её не нужно вновь «раздувать» и инициализировать. Не совсем понятно, как это работает? Проанализируйте приложение-пример, там демонстрируется использование панели.
Недостатки
Начав реализовывать навигацию на основе фрагментов, было бы очень неприятно углубиться в разработку и столкнуться с какими-то непредвиденными, трудноразрешимыми проблемами, связанными с дополнительной сложностью использования фрагментов, сторонними библиотеками и разными версиями ОС. А если придётся рефакторить всё то, что мы уже понаписали?
Действительно, придётся решать проблемы, связанные со вложенными фрагментами, а также со сторонними библиотеками, тоже использующими фрагменты, наподобие ShinobiControls, ViewPagers и FragmentStatePagerAdapters.
Надо сказать, что может потребоваться немало времени на приобретение достаточного опыта, позволяющего справиться с затруднениями. Правда, их наличие чаще всего обусловлено неполным пониманием особенностей работы с фрагментами, а не порочностью самой идеи. Возможно, у вас уже достаточно опыта и вы вообще не столкнётесь ни с какими проблемами.
Так что единственным недостатком, достойным упоминания, является нехватка хороших библиотек, в которых были бы отражены все сложные сценарии из сложных приложений, чья навигация построена на фрагментах.
Заключение
В этой статье мы рассмотрели один из подходов к реализации навигации в Android-приложениях. Было проведено сравнение с традиционным подходом, подразумевающим использование активностей, выявившее ряд преимуществ в сравнении с «классикой».
На тот случай, если вы до сих пор не посмотрели приложение-пример, добро пожаловать на GitHub. Можете свободно форкать или дополнять его более удачными и наглядными решениями.
Источник