- Урок 26. Navigation. Параметры навигации
- destination Fragment
- Attributes
- Arguments
- Actions
- Deep links
- destination Activity
- Attributes
- Arguments
- Deep links
- Action
- Атрибуты
- Transitions
- Argument Default Values
- Pop Behaviour
- Launch Options
- NavOptions
- Navigation Architecture Component. Практический взгляд
- Постановка задачи
- Реализация
- Выводы
- Тестовый проект
Урок 26. Navigation. Параметры навигации
В этом уроке разбираемся, как задавать параметры при навигации.
Полный список уроков курса:
В графе у нас есть три основных объекта, у которых мы можем задавать параметры: это destination Fragment, destination Activity и action.
Рассмотрим каждый из них подробно.
destination Fragment
Attributes
Type — это тип destination, в данном случае Fragment.
Label — текстовое описание. Его можно получить в коде.
Также оно будет использовано в случае интеграции с Navigation Drawer. Об этом будет следующий урок.
В качестве значения вы можете указать строковый ресурс, например: @string/fragment3_label
ID — уже знакомый нам атрибут, используется в методе navigate для навигации к этому destination. ID генерируется автоматически, но всегда можно ввести свое более осмысленное значение.
Class — класс фрагмента.
Set Start Destination — этой кнопкой можно destination сделать стартовым. Он будет отображаться первым при запуске.
Arguments
Возможность задать аргументы с значениями по умолчанию. Это мы подробно рассмотрели в прошлом уроке.
Actions
Список action, выходящих из этого destination. На скриншоте выше только один action, который ведет в SecondActivity. Но их может быть несколько.
Deep links
Об этом поговорим в отдельном уроке.
destination Activity
Attributes
Type, Label, ID, Class — аналогичны атрибутам destination Fragment.
Action и Data — соответствуют стандартным полям action и data в Intent классе. Заполняете их здесь и в Activity достаете из getIntent().getAction() и getIntent().getData().
Pattern — это примерно то же, что и Data, но с параметрами. Т.е. вы здесь задаете свой Uri и в нем указываете имена параметров в фигурных скобках.
На скриншоте выше я указал Uri: content://media/photo/
У него два параметра folder и id. От нас потребуется при вызове Activity передать значения для этих параметров через Bundle.
Параметры должны быть строковые.
В результате в Activity метод getIntent().getData() вернет нам: content://media/photo/camera/100.jpg
Arguments
Аналогичны аргументам destination Fragment.
Deep links
Об этом поговорим в отдельном уроке.
Обратите внимание, что нет списка Actions. Потому что граф действует только в пределах своего Activity. Переход в другое Activity — это уход из этого графа и дальнейшие перемещения будут определяться уже другим графом.
И нет кнопки Set Start Destination. Выходная из графа точка не может быть стартовой.
Action
Напомню, что action — это возможность указать дополнительные параметры при вызове destination. Рассмотрим эти параметры.
Пример: action из Fragment2 в Fragment3
Атрибуты
C Type и ID все понятно.
Destination — показывает, куда ведет action. Т.е. какой destination будет открыт, если вызвать метод NavController.navigate с ID этого action.
Transitions
Возможность задать анимацию перехода между destination.
Этот action ведет с Fragment2 на Fragment3. Соответственно:
Enter — анимация для появления Fragment3
Exit — анимация для исчезания Fragment2.
А когда возвращаемся с Fragment3 на Fragment2, то
Pop Enter — анимация появления Fragment2
Pop Exit — анимация исчезанияFragment3
Под капотом эти анимации просто передаются в FragmentTransaction:
Argument Default Values
Аргументы берутся из destination, в который ведет action. Здесь можно задать им значения по умолчанию.
Pop Behaviour
Допустим у нас есть три destination.
Мы поочередно их открываем в Activity: fragment1 > fragment2 > fragment3.
По каким то причинам нам надо, чтобы при возврате назад из fragment3 мы сразу попадали в fragment1 (минуя fragment2). Для этого мы создаем action, который ведет из fragment2 в fragment3 и укажем ему Pop To = fragment1. Теперь при вызове этого action система сбросит backStack до указанного в Pop To фрагмента.
В результате при возврате из Fragment3 попадаем в Fragment1.
Если включен чекбокс Inclusive, то destination, указанный в Pop To также будет закрыт и мы попадем на destination, который был перед ним.
Launch Options
Эти параметры зависят от того, куда ведет action: в Activity или в фрагмент.
Рассмотрим сначала для случая, когда action ведет в Activity.
Single Top — добавляет в Intent вызова Activity флаг Intent.FLAG_ACTIVITY_SINGLE_TOP
Document — добавляет в Intent вызова Activity флаг Intent.FLAG_ACTIVITY_NEW_DOCUMENT
Clear Task — cбрасывает стэк текущего графа до стартового destination. Добавляет в Intent вызова Activity флаг Intent.FLAG_ACTIVITY_CLEAR_TASK. Не очень понятный режим. Возможно еще просто не доведен до ума.
Если же action ведет в фрагмент:
Single Top — если текущий фрагмент тот же, что и вызываемый, то вызов будет проигнорирован.
Document — похоже, что никак не используется.
Clear Task — стэк текущего графа очищается до стартового фрагмента. Транзакция перехода в новый фрагмент не добавляется в backStack. Соответственно, вызываемый фрагмент заменяет текущий и становится единственным.
NavOptions
Все выше рассмотренные параметры можно задавать программно в объекте NavOptions и далее передавать этот объект в метод navigate.
Источник
Navigation Architecture Component. Практический взгляд
На недавнем Google IO 2018 в числе прочего было представлено решение, помогающее в реализации навигации в приложениях.
Сразу бросилось в глаза то, что граф навигации можно просмотреть в UI редакторе, чем-то напоминающем сториборды из iOS-разработки. Это действительно интересно и ново для Android-разработчиков.
А так как я еще являюсь создателем довольно популярной библиотеки Cicerone, которая позволяет работать с навигацией на андроиде, то я получил множество вопросов на тему того, пора ли мигрировать на решение Google, и чем оно лучше/хуже. Поэтому, как только появилось свободное время, я создал тестовый проект и стал разбираться.
Постановка задачи
Я не буду повторять вводные слова про новый архитектурный компонент Navigation Architecture Component, про настройку проекта и основные сущности в библиотеке (это уже сделал Саша Блинов), а пойду с практической стороны и попробую библиотеку в бою, изучу возможности и применимость к реальным кейсам.
Я накидал на листе бумаги граф экранов с переходами, которые хотел бы реализовать.
Хочу отметить, что навигация в данном случае рассматривается в пределах одного Activity, такой подход я практикую уже не на первом проекте, и он себя отлично показал (обсуждение этого подхода — не цель данной статьи).
Таким графом я постарался описать наиболее частые кейсы, которые встречаются в приложениях:
- A — стартовый экран. С него приложение начинает работу при запуске из меню.
- Есть несколько прямолинейных переходов: A->B, B->C, C->E, B->F
- Из A можно запустить отдельный флоу a-b
- Из F можно переходить на новые экраны F, выстраивая цепочку одинаковых F-F-F-.
- Из F тоже можно запустить флоу a-b
- Флоу a-b — это последовательность двух экранов, где с экрана b можно выйти из флоу на тот экран, с которого был произведен запуск флоу
- Из экрана C можно перейти на D, заменив экран, то есть, из цепочки A-B-C, перейти к A-B-D
- Из экрана E можно перейти на D, заменив два экрана, то есть из цепочки A-B-C-E, перейти к A-B-D
- С экрана E можно вернуться на экран B
- С экрана D можно вернуться на экран A
Реализация
После переноса такого графа в код тестового Android-приложения получилось красиво:
На первый взгляд, очень похоже на ожидаемый результат, но давайте по порядку.
1. Стартовый экран устанавливается свойством
работает как и предполагалось, приложение запускается именно с него.
2. Прямолинейные переходы выглядят и работают тоже правильно
3. и 5. Вложенная навигация легко описывается и запускается как простой экран. И тут первое интересное свойство: я ожидал, что вложенная навигация будет как-то связана с вложенными фрагментами, но оказалось, что это просто удобное представление в UI-графе, а на практике все работает на одном общем стеке фрагментов.
4. Запуск цепочки одинаковых экранов делается без проблем. И тут можно воспользоваться специальным свойством перехода
При указании этого режима перед переходом на новый экран будет проверка текущего верхнего экрана в стеке. Если новый и текущий экраны совпадают по типу, текущий экран будет удален, то есть верхний экран будет постоянно пересоздаваться, не увеличивая стек.
Тут скрыт первый баг: если открыть несколько раз экран F с параметром app:launchSingleTop=»true» , а потом нажать кнопку «назад», то будет произведен возврат на экран B, но экран F останется «висеть» сверху.
Могу предположить, что это происходит из-за того, что фрагмент менеджер внутри сохранил транзакцию с B на первый инстанс F, а мы его уже сменили на новый, который не будет ему известен.
6. Отдельный флоу из двух экранов работает прекрасно, но проблема в том, что нельзя явно описать поведение, что из второго экрана этого флоу надо вернуться на запустивший экран!
Здесь мы подошли к важному свойству UI-графа: на графе нельзя описать переходы назад! Все стрелки — это создание нового экрана и переход на него!
То есть чтобы сделать переход «назад» по стеку, надо обязательно работать с кодом. Самое простое решение для нашего случая — это вызвать два раза подряд popBackStack() , новая навигация здесь ничем не помогает. А для более сложных флоу надо будет выдумывать другие решения.
7. Заменить экран C на D можно, указав в свойствах перехода
как видите, здесь мы жестко завязали логику перехода между C и D на предыдущий экран B. Тоже не самое лучшее место в новой библиотеке.
8. Аналогично и с переходом с E на D — добавляем app:popUpTo=»@+id/BFragment» , и работает как мы хотели, но с привязкой к экрану B
9. Как я объяснил выше, возвратов на UI-графе нет. Поэтому возврат с экрана E на экран B можно сделать только из кода, вызвав popBackStack(R.id.BFragment) до необходимого экрана. Но если вам не обязательно именно вернуться на экран B, а допустимо создать новый экран B, то делается это переходом на UI-графе с установкой двух параметров:
Таким образом будет скинут стек до экрана B, включая его самого, а потом сверху создан новый экран B.
В реальных проектах такая логика конечно не подойдет, но как возможность — интересно.
10. Возврат на экран A, как вы уже поняли, невозможно описать на графе. Надо делать его из кода. Но по примеру предыдущего кейса, можно скинуть все экраны и создать новый экран A в корне. Для этого создаем переход из D на A и добавляем специальное свойство
Теперь при вызове такого перехода, сначала будет очищен весь стек фрагментов.
Остался один неизученный параметр из UI редактора навигации:
Я так и не понял его предназначение, а скорее всего он просто не работает так, как заявлено в документации:
Launch a navigation target as a document if you want it to appear as its own entry in the system Overview screen. If the same document is launched multiple times it will not create a new task, it will bring the existing document task to the front.
If the user presses the system Back key from a new document task they will land on their previous task. If the user reached the document task from the system Overview screen they will be taken to their home screen.
Выводы
Из моих экспериментов и небольшого изучения исходников можно сделать вывод: новая навигация — это просто UI обертка работы с фрагмент менеджером (для случая приложения в одном Activity). Всем стрелкам-переходам выдается уникальный ID и набор параметров, по которым совершается транзакция фрагментов на старом «добром» фрагмент менеджере. Библиотека еще в глубокой альфе, но уже сейчас заметны баги, которые прямо указывают на известные проблемы фрагмент менеджера.
Если нам надо как-то повлиять на стек фрагментов и сделать возврат дальше предыдущего экрана, то придется работать по старинке с фрагмент менеджером.
Если вызвать навигацию при свернутом приложении, приложение упадет с классическим
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Чтобы совершить навигацию нужен доступ к view для поиска navController
Navigation.findNavController(view)
Именно эти проблемы и решает библиотека Cicerone, поэтому сравнивать их мало смысла. Зато можно объединить! Потому что у Google-навигации есть и полезные стороны:
- Визуальный граф. Для небольших проектов может быть очень полезной подсказкой.
Описание входных параметров экрана и проверка без запуска приложения прямо в студии.
Для этого надо подключить дополнительный Gradle плагин и описывать обязательные параметры в xml свойствах экрана.
Мне не удалось проверить эту фичу, но есть подозрение, что она так же сделана в лоб, поэтому просто будет другой корневой экран, а не полная цепочка экранов, как хотелось бы. Но это не точно.
Тестовый проект
Все эксперименты можно посмотреть и потрогать на Github
Источник