Android navigation что это такое

Урок 24. Navigation Architecture Component. Введение

В этом уроке рассмотрим простой пример использования Navigation Architecture Component.

Полный список уроков курса:

На Google IO был представлен Navigation Architecture Component. На сегодняшний день (28.05.2018) он в альфа версии, поэтому использовать его в рабочих проектах пока рановато, но ознакомиться уже можно. Ближайшие уроков 5 будут о нем.

Я подробно просмотрел исходники этого компонента, чтобы точно понимать, что он делает. Под капотом там все те же startActivity и FragmentManager. Navigation Architecture Component — это обертка над этими стандартными механизмами, которая призвана упорядочить и упростить навигацию в приложении.

Для полноценной работы с Navigation нам понадобится Android Studio 3.2. Пока что есть только Canary сборка, она вполне подойдет.

Navigation функционал может быть выключен по умолчанию. Проверьте настройки студии Settings > Experimental > Enable Navigation Editor. Перезапустите студию после включения этого чекбокса.

dependencies в build.gradle файле модуля:

Рассмотрим простой пример с MainActivity и тремя фрагментами: Fragment1, Fragment2, Fragment3. MainActivity будет поочередно отображать в себе фрагменты.

Для этого нам понадобятся следующие Navigation инструменты:

NavController — этот основной механизм Navigation Component. Именно его мы будем просить показывать на экране фрагменты. Но чтобы он мог это делать, он должен иметь список фрагментов и контейнер, в котором он будет эти фрагменты отображать.

NavGraph — это список фрагментов, который мы будем создавать и наполнять. NavController сможет показывать фрагменты только из этого списка. Далее будем называть его графом.

NavHostFragment — это контейнер. Внутри него NavController будет отображать фрагменты.

Еще раз, кратко, для понимания: контроллер в контейнере отображает фрагменты из графа.

Начнем с создания графа (NavGraph). Это обычный resource файл с типом Navigation.

После создания он пуст:

Давайте добавлять фрагменты. В терминологии Navigation они называются destination.

Жмем кнопку добавления, студия показывает нам фрагменты и Activity, которые есть в проекте. Добавляем три фрагмента.

Слева видим список всех destination в этом графе. Пометкой Start отмечен стартовый destination, который сразу будет отображен при запуске приложения. В нашем случае это Fragment1.

Посередине отображены те же destination, но уже не списком, а в их реальном виде, с использованием их layout. Значком домика помечен стартовый destination. Для всех трех фрагментов я создал одинаковые layout: название фрагмента и пара кнопок. Позже будем использовать эти кнопки для навигации.

Справа находится панель атрибутов для текущего выделенного destination. О них мы подробно поговорим позже. Пока что нас интересует атрибут ID. Этот ID нам надо будет сообщать контроллеру (NavController), чтобы он отобразил соответствующий фрагмент. Этот ID кстати отображается в панели посередине. Над каждым фрагментом можно увидеть его ID.

Ок, граф создан. Теперь в MainActivity надо добавить контейнер (NavHostFragment), в котором NavController будет отображать фрагменты.

В activity_main добавляем NavHostFragment:

Контейнер готов. Остается где-то взять контроллер (NavController). Тут нам поможет контейнер. Он при создании сам создаст контроллер и чуть позже поделится им с нами.

Обратите внимание, что в атрибуте navGraph мы указали созданный ранее граф main_graph. Контейнер передаст этот граф контроллеру.

Переходим к коду.

Чтобы попросить контроллер у контейнера, используем метод Navigation.findNavController с указанием ID контейнера. Этот метод по ID найдет контейнер NavHostFragment и возьмет у него контроллер.

Код в MainActivity.java

Теперь мы можем использовать этот контроллер для навигации по фрагментам. Для этого есть два метода:

navigate(int resId) — чтобы открыть какой-либо фрагмент из графа, надо у контроллера вызвать метод navigate и передать ему ID destination. Контроллер просмотрит граф, определит какому фрагменту в графе соответствует ID и отобразит этот фрагмент.

popBackStack — возврат на шаг назад, на предыдущий фрагмент.

Как вы уже видели, в каждом из трех фрагментов есть кнопки Back и Next. По нажатию на кнопку Next мы будем открывать следующий фрагмент. А по нажатию на кнопку Back будем возвращаться на предыдущий. Я использовал колбэки и обработку нажатий на эти кнопки вытащил в Activity. Соответственно в MainActivity у меня 6 методов (3 фрагмента, 2 кнопки в каждом)

В этих методах мы и будем работать с контроллером.

По названию метода понятно для какой кнопки какого фрагмента он является обработчиком.

Например, по нажатию на Next в Fragment1 мы просим контроллер открыть destination с Контроллер найдет этот destination в графе, увидит, что это фрагмент Fragment2 и в контейнере отобразит этот фрагмент.

Читайте также:  Зарядка для андроида ремонт

Аналогично по нажатию на Next в Fragment2 мы просим открыть destination с который в графе соответствует фрагменту Fragment3.

По нажатию на кнопки Back, мы возвращаемся на шаг назад.

Запускаем приложение. При старте сразу отобразится Fragment1, потому что он является стартовым в графе.

Системная кнопка Back тоже работает и выполняет шаг назад. Это происходит благодаря атрибуту

который мы указали в контейнере (NavHostFragment). Контейнер перехватывает нажатия и показывает предыдущий фрагмент.

Если установить его значение в false, то контейнер больше не будет обрабатывать системную кнопку Back, и Activity будет закрываться.

Action

При вызове destination нам может понадобиться задать некоторые параметры, например, анимацию, аргументы и т.п. Для этого используется action.

Давайте создадим action, который будет выполнять переход от fragment1 к fragment2

Для destination fragment1 мы создали action, который ведет в destination fragment2.

У action есть различные параметры, которые мы можем настраивать в редакторе графа. Они будут использованы при переходе от destination fragment1 к destination fragment2.

Мы разберем их подробно в следующих уроках. Пока нас снова интересует только значение атрибута ID. Мы можем использовать его при вызове метода navigate, чтобы вызвать action. Давайте сделаем это по нажатию на кнопку Next в Fragment1.

Контроллер сделает следующее:
1) возьмет текущий destination (который сейчас отображается в контейнере, т.е. destination fragment1)
2) найдет у него action с >3) определит, что этот action ведет в destination fragment2
4) определит, что destination fragment2 — это фрагмент Fragment2
5) отобразит Fragment2 и при этом применит параметры, которые были заданы в action_fragment1_to_fragment2

Если мы попытаемся вызвать action не находясь в destination, которому этот action принадлежит, то будет ошибка. Т.е. action action_fragment1_to_fragment2 мы можем вызывать только находясь в destination fragment1, потому что при создании action мы рисовали его из destination fragment1.

Из одного destination можно создать несколько action:

Activity

В качестве destination мы можем использовать не только фрагменты, но и Activity.

В этом же примере я создал SecondActivity и фрагменты Fragment4 и Fragment5. Будем вызывать их из Fragment3, находящемся в MainActivity.

Открываем граф main_graph и добавляем SecondActivity, как новый destination.

destination создан, его >

По нажатию на кнопку Next в Fragment3 будем вызывать этот destination

Контроллер найдет в графе, что destination с таким ID соответствует SecondActivity и запустит его.

На этом полномочия графа main_graph заканчиваются. В новом Activity нам нужен новый граф.

Создаем second_graph и добавляем туда Fragment4 и Fragment5

Fragment4 — стартовый, он будет отображен при открытии SecondActivity.

В layout activity_second добавляем контейнер NavHostFragment и указываем граф second_graph

В SecondActivity находим контроллер и в обработчиках нажатий кнопок фрагментов используем его для навигации.

Из фрагмента Fragment3 переходим в SecondActivity. Там сразу открывается Fragment4, т.к. он стартовый. Из него переходим в Fragment5 и обратно. А вот возвращаться из SecondActivity в MainActivity приходится с помощью системной кнопки Back. Контроллер в SecondActivity работает только в пределах этого Activity. Он ничего не знает за его пределами. Он не знает, что делать, когда вызывается popBackStack в стартовом фрагменте, т.е. в Fragment4. Тут уже нам надо самим. Например, можно в onFragment4BackClick вызывать метод finish, чтобы закрыть Activity.

Метод navController.popBackStack возвращает boolean. Если контроллер сам смог вернуться на шаг назад, то он вернет true. Если же он не знает, что делать, то вернет false и в этом случае мы сами можем обработать эту ситуацию.

destination

Напоследок пару слов о понятии destination в Navigation Component. В грАфе у каждого destination есть ID, и мы указываем этот ID в методе navigate, когда просим контроллер открыть destination. При этом нам не важно, чем является в графе этот destination — Activity или фрагментом. Это забота контролера. Он сам это определит и вызовет либо startActivity, либо будет работать с FragmentManager.

Например, у нас в приложении есть экран конфигурации. Это фрагмент ConfigFragment. В графе у нас этот фрагмент фигурирует как destination с И мы открываем его вызовом метода navController.navigate(R.id.configScreen).

Внезапно мы решаем, что надо экран конфигурации вынести в отдельное ConfigActivity. Создаем это Activity, переносим все туда и добавляем его в граф вместо ConfigFragment, под тем же >

Читайте также:  Наушники через микро usb подключить андроид

При этом в приложении вообще никак не меняется код вызова экрана конфигурации. Это так и остается вызов метода navigate с указанием Но теперь контроллер будет открывать не ConfigFragment в текущем контейнере, а запустит новое Activity, потому что мы настроили это в графе.

А вот так выглядит содержимое графа main_graph.xml:

Три фрагмента, одно Activity и у первого фрагмента есть action, который ведет во второй фрагмент.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

На недавнем Google IO 2018 в числе прочего было представлено решение, помогающее в реализации навигации в приложениях.

Сразу бросилось в глаза то, что граф навигации можно просмотреть в UI редакторе, чем-то напоминающем сториборды из iOS-разработки. Это действительно интересно и ново для Android-разработчиков.

А так как я еще являюсь создателем довольно популярной библиотеки Cicerone, которая позволяет работать с навигацией на андроиде, то я получил множество вопросов на тему того, пора ли мигрировать на решение Google, и чем оно лучше/хуже. Поэтому, как только появилось свободное время, я создал тестовый проект и стал разбираться.

Постановка задачи

Я не буду повторять вводные слова про новый архитектурный компонент Navigation Architecture Component, про настройку проекта и основные сущности в библиотеке (это уже сделал Саша Блинов), а пойду с практической стороны и попробую библиотеку в бою, изучу возможности и применимость к реальным кейсам.

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

Хочу отметить, что навигация в данном случае рассматривается в пределах одного Activity, такой подход я практикую уже не на первом проекте, и он себя отлично показал (обсуждение этого подхода — не цель данной статьи).

Таким графом я постарался описать наиболее частые кейсы, которые встречаются в приложениях:

  1. A — стартовый экран. С него приложение начинает работу при запуске из меню.
  2. Есть несколько прямолинейных переходов: A->B, B->C, C->E, B->F
  3. Из A можно запустить отдельный флоу a-b
  4. Из F можно переходить на новые экраны F, выстраивая цепочку одинаковых F-F-F-.
  5. Из F тоже можно запустить флоу a-b
  6. Флоу a-b — это последовательность двух экранов, где с экрана b можно выйти из флоу на тот экран, с которого был произведен запуск флоу
  7. Из экрана C можно перейти на D, заменив экран, то есть, из цепочки A-B-C, перейти к A-B-D
  8. Из экрана E можно перейти на D, заменив два экрана, то есть из цепочки A-B-C-E, перейти к A-B-D
  9. С экрана E можно вернуться на экран B
  10. С экрана 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 свойствах экрана.

  • Привязка Deep Link к запуску любого экрана из графа.
    Мне не удалось проверить эту фичу, но есть подозрение, что она так же сделана в лоб, поэтому просто будет другой корневой экран, а не полная цепочка экранов, как хотелось бы. Но это не точно.
  • Возможность создавать переиспользуемые части графа — “Nested Graph”. То есть выносить некоторую часть графа во вложенный граф, который можно запускать из нескольких мест.
  • Тестовый проект

    Все эксперименты можно посмотреть и потрогать на Github

    Источник

    Оцените статью