Хабрахабр

Навигация с архитектурными компонентами от Google. Часть 1. Знакомство

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

В данной статье мы разберемся, что вообще требуется от фреймворка для навигации, познакомимся с тем, как устроено решение от 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 и вообще всё, что вы подразумеваете под экраном навигации.

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

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

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

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

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

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

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

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

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

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

В корневом **build.gradle**

buildscript dependencies { classpath 'com.android.tools.build:gradle:3.2.0-alpha14' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }
}

В app/build.gradle

dependencies { def nav_version = '1.0.0-alpha01' implementation "android.arch.navigation:navigation-fragment:$nav_version" implementation "android.arch.navigation:navigation-ui:$nav_version" //другие зависимости
}

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

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

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> </navigation>

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

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

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

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

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

В редакторе это выглядит так

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

  • ID — идентификатор Action: его мы будем использовать для переключения экранов.
  • Destination — ID Destination, на который ведёт Action.
    Дополнительно имеется 4 группы параметров:
  • Transaction — анимация для переключения. Можно указать различные виды для действий: 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, и устанавливаем ему атрибуты: Навигационный граф есть, теперь привязываем его к контейнеру.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/container" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:navGraph="@navigation/nav_graph" app:defaultNavHost="true" /> </FrameLayout>

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

MainActivity

override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()

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

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

NavHostFragment.findNavController(Fragment) Navigation.findNavController(View) Navigation.findNavController(Activity, @IdRes int viewId)

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

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

HomeFragment

fragment_home_button_dashboard.setOnClickListener { activity?.let { findNavController(it, R.id.nav_host_fragment) .navigate(R.id.action_homeFragment_to_dashboardFragment) }
}

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

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

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

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

nav_graph.xml

<action android:id="@+id/action_notificationFragment_to_dashboardFragment" app:destination="@id/dashboardFragment" app:enterAnim="@anim/nav_custom_enter_anim" app:popUpTo="@+id/homeFragment" />

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

Приоритет 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.
  • Как всё устроено внутри и насколько хорошо работает с жизненным циклом.
  • Как сделать навигацию на вью.
  • Как собрать навигационный граф из кода.
  • Как тестировать навигацию.
  • На сколько всё это готово к бою и какие абстракции надо добавить.

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

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

Как заставить Android Studio отображать навигационный граф

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть