Navigation architecture component android

Урок 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 тоже работает и выполняет шаг назад. Это происходит благодаря атрибуту

Читайте также:  Phoenix как удалить с андроида

который мы указали в контейнере (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, под тем же >

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

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

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

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

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

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

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

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

Читайте также:  Как ищут украденные телефоны андроид

Источник

Одной из проблем, с которыми сталкивается разработчик немного подразросшегося приложения — навигация между экранами. Когда сценарии становятся нелинейными, уже тяжело обойтись стандартными startActivity и changeFragment. Эту проблему каждый решал по-своему: делал какое-то свое решение для навигации, использовал стороннее (к примеру, Cicerone) или же оставлял все как есть и городил кучу флагов и if else. Это очень огорчало инженеров Google, и вот уже на Google I/O 2018 появилось решение Navigation, которое идёт в комплекте с остальными Архитектурными компонентами!

В данной статье мы разберемся, что вообще требуется от фреймворка для навигации, познакомимся с тем, как устроено решение от Google и какими абстракциями оно оперирует.

Требования к навигации
Начнём с требований, которые мы предъявляем фреймворку для навигации. В цикле статей мы будем к ним возвращаться, когда поймём, что они выполняются или нет.

Приоритет 1 (Обязательно):
[1] Отвязка от жизненного цикла.
[2] Возможность осуществлять вложенную навигацию.
[3] Автоматически открывает предыдущий экран при команде «Назад».
[4] Есть возможность добавить анимацию для смены экранов.
[5] Передача аргументов.
[6] Возможность перестраивать навигацию в рантайме.
[7] Поддержка Shared element.
[8] Возможность открывать и закрывать цепочку экранов.
Приоритет 2 (желательно):
[9] Удобный механизм работы с deeplink.
[10] Возможность имплементации на Activity, Fragment, View.
[11] Тестирование навигации.
[12] Гибкость при изменениях.
[13] Возможность навигации из бизнес-логики.
[14] Возможность подменять экраны, которые находятся в навигационном стеке.
[15] Проверка аргументов во время компиляции.
[16] Имеет визуальное представление для простого проектирования.

Постулаты навигации

Итак, для начала разберёмся с новыми принципами, которые диктует нам Google:

Принцип 1. Фиксированная точка старта
Все ссылки в приложение ведут в одну точку.Это означает, что мы всегда стартуем с одного экрана (по сути это SplashScreen), а дальше уже решаем, куда идти в приложении.

Принцип 2. Навигация работает в виде стека LIFO
Мы можем только делать операции push и pop, а экраны, которые лежат в середине стека, неприкосновенны.

Принцип 3. Кнопки Back и Up работают одинаково
Наконец-то стрелка тулбара и хардварная «Назад» будут работать одинаково! Больше не смущаем пользователей!

Принцип 4. Диплинки в приложение порождают стек экранов, аналогичный тому, если бы мы дошли до экрана самостоятельно
Если мы открыли приложение по диплинку, нажимая кнопку «Назад», мы пройдем по всем экранам, аналогично тому, как если бы мы их открывали без диплинка.

Определения

Для быстрого погружения можно скачать сэмпл с Github, чтобы попутно смотреть всё на практике.

В конце статьи также написано как побороть Android Studio, если сразу же не завелось.

Итак, как же устроен новый навигационный фреймворк. Он состоит из следующих компонентов:

Destination — экран, который открывается при навигации. Сохраним именно это обозначение, чтобы удобно было обращаться к документации. Это может быть Fragment, Activity, View и вообще всё, что вы подразумеваете под экраном навигации. В дальнейшем я буду говорить о фрагментах, но не забывайте, что можно использовать не только их.

NavHost интерфейс — это view-контейнер, по которому переключаются экраны (Destination), когда пользователь перемещается по приложению. Реализация по умолчанию для него — это NavHostFragment. В него посредством xml устанавливается граф для навигации.

NavGraph — это xml-предоставление навигации. В нём описываются экраны Destination и связи между ними.

NavController — сущность, которая осуществляет механизм навигации. Именно к нему мы будем обращаться, когда будем переключать между собой экраны (Destination). NavController устанавливается в NavHost. Если мы имеем дело с готовым NavHostFragment, то вся логика создания и предоставления NavController уже сделана за нас.

Action — команда переключения на другой экран. Её отправляем NavController-у для смены Destination

FragmentNavigator — внутренний класс, который инкапсулирует в себе транзакции фрагментов.

Принцип работы

Построенный NavGraph устанавливается в NavHostFragment (как частный случай NavHost). Теперь он знает, какие есть Action и какие экраны им соответствуют.
NavHostFragment предоставляет NavController, который сообщает ему о предстоящей транзакции. К NavController мы обращаемся из кода, отдавая нужную команду Action. За конечное переключение экранов отвечает FragmentNavigator, с которым взаимодействует NavHostFragment.

Имплементация

Настало время разобраться, как с этим зверем работать.

Для начала подключаем все необходимые библиотеки и плагины. Актуальную информацию проверяйте в соответствующем разделе на сайте Google.

Создаём навигационный граф
Далее создаём карту навигации. Для неё зафиксирован отдельный тип ресурса — navigation, для которого предусмотрен специальный редактор.

Для этого создаём файл res/navigation/nav_graph.xml с содержимым. Можно это сделать не руками, а через стандартный конструктор

Переключаем редактор на вкладку Design, и в нём мы накидываем экраны (Destination) и связываем их при помощи стрелок Actions.
Механика очень интуитивна и похожа на взаимодействие с ConstraintLayout. Если возникли проблемы, то можно посмотреть на детальный гайд от Google. (Выполнено требование [3]).

Читайте также:  Кликеры с читами для андроид

Конструктор состоит из двух видов сущностей: Destination (экраны) и Action (стрелочки).

Destination имеет набор атрибутов:

  • ID — идентификатор экрана, для связи с другими посредством Action.
  • Class — класс данного экрана (к примеру, LoginFragment).
    Дополнительно имеется три группы параметров:
  • Arguments — аргументы, которые его параметризуют.
  • Actions — команды, определяющие, куда можно перейти с текущего экрана.
  • Deeplinks — диплинки для перехода на экран из-за пределов приложения.

Также существует возможность сделать этот экран стартовым.

Action имеет набор атрибутов.

  • ID — идентификатор Action: его мы будем использовать для переключения экранов.
  • Destination — ID Destination, на который ведёт Action.
    Дополнительно имеется 4 группы параметров:
  • Transition — анимация для переключения. Можно указать различные виды для действий: Enter, Exit, Pop Enter, Pop Exit. Анимации берутся из xml ресурсов. Легко определить свои, для этого просто добавьте соответствующий ресурс в папку res/anim. (См пример с nav_custom_enter_anim.xml)
  • ArgumentDefaultValue — значение аргументов для фрагмента по умолчанию.
  • PopBehavior — как навигации вести себя при закрытии фрагмента. К примеру, мы можем захотеть закрыть все экраны из стека и вернуться к стартовому. (См пример с Action action_notificationFragment_to_dashboardFragment — в нем мы как раз переходим на главный экран). (Выполнено требование [16])
  • LaunchOption — опции по работе со стеком, напоминают launch Mode при работе со стеком Activity.

В итоге получается картинка похожая на титульную из статьи.

Привязываем навигационный граф к контейнеру

Навигационный граф есть, теперь привязываем его к контейнеру. Для этого добавляем в xml MainActivity, который выступает холдером для всей навигации NavHostFragment, и устанавливаем ему атрибуты:

Предпоследняя строчка говорит о том, что навигация будет осуществляться по созданному нами навигационному графу.
Последняя строчка означает, что NavHostFragment установлен дефолтным, это гарантирует обработку системного ‘back’. Для выполнения принципа 3 навигации также необходимо переопределить в Activity действие на стрелку назад:

Выполнение команды навигации

Для того, чтобы переключиться на другой фрагмент, нам необходимо получить Navigator.
Это возможно сделать несколькими способами.

По сути, это команды из разных контекстов для поиска View, имплементирующую NavHost, и получения у неё NavController-а.

Далее у полученного NavController вызывается один из методов для навигации.

Для осуществления навигации при помощи NavController
у нас имеются в распоряжении:

  1. Несколько перегруженных методов navigate(). В них параметрами являются
    • ActionId — тот самый ID Action из xml
    • Bundle — набор аргументов,
    • NavOptions — аргументы для Action, по которым определяется анимация, стек и пр.),
    • NavDirections — абстракция, оборачивающая Bundle и ActionId
  2. Пара перегруженных методов popBackStack() для перехода к экранам из текущего стека.

В нашем примере используются только Action для перехода вперед. Команда ‘Back’ обрабатывается автоматически и вызывает обратное действие у Action (тот самый атрибут PopBehavior)

Так, при переходе назад после Action (команды) action_notificationFragment_to_dashboardFragment с экрана DashboardFragment мы попадаем не на NotificationFragment, а на HomeFragment.

Итоги
На первый взгляд, фреймворк от Google не вызвал резкого осуждения и выглядит как нечто, на что необходимо посмотреть внимательнее.
В этой части мы удостоверились, что выполнены требования [3] и [16]. После первого взгляда, таблица наших требований к навигации для решения от Google выглядит так:

Приоритет 1 (обязательно):
[1] Отвязка от жизненного цикла.
[2] Возможность осуществлять вложенную навигацию.
[3] Автоматически открывает предыдущий экран при команде «Назад» [✓].
[4] Есть возможность добавить анимацию для смены экранов.
[5] Передача аргументов.
[6] Возможность перестраивать навигацию в рантайме.
[7] Поддержка Shared element.
[8] Возможность открывать и закрывать цепочку экранов.
Приоритет 2 (желательно):
[9] Удобный механизм работы с deeplink.
[10] Возможность имплементацию на Activity, Fragment, View.
[11] Тестирование навигации.
[12] Гибкость при изменениях.
[13] Возможность навигация из бизнес логики.
[14] Возможность подменять экраны которые находятся в навигационном стеке.
[15] Проверка аргументов во время компиляции.
[16] Имеет визуальное представление для простого проектирования [✓].

Что дальше?

В следующих частях рассмотрим (может быть порядок будет меняться):

  • Транзакции для открытия фрагментов с аргументами.
  • Диплинки.
  • Встроенные средства для работы с BottomNavigation и Toolbar.
  • Разные типы навигации: вложенную и с несколькими Activity.
  • Как всё устроено внутри и насколько хорошо работает с жизненным циклом.
  • Как сделать навигацию на вью.
  • Как собрать навигационный граф из кода.
  • Как тестировать навигацию.
  • На сколько всё это готово к бою и какие абстракции надо добавить.

Как запустить прямо сейчас

Описанный пример лежит у меня на Github. Для того, чтобы его запустить, необходима Android Studio не ниже 3.2 Canary.

У меня завелось только с версией SDK Build Tools 28.0.0-rc1 (на 28.0.0-rc2 все отправлялось в бесконечную загрузку)

Если вдруг и это не помогло, то сделайте clean — меня в паре случаев выручало.

Источник

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