Navigation component android documentation

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.

Источник

Урок 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.

Читайте также:  Android language setting app

Посередине отображены те же 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

Читайте также:  Адгуард премиум для андроид с вечным ключом 4pda

Если мы попытаемся вызвать 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

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