Вся правда об использовании навигационной библиотеки Jetpack в модульных проектах

⚠️ Это не обучающая статья. Это, скорее, мой манифест. В нем я выразил свои впечатления от использования навигационной библиотеки Jetpack в модульных проектах. Плюсы, минусы, альтернативы. Обращаю ваше внимание на то, что я сфокусировался на навигации по фрагментам/действиям, а не на Composables-функциях. Я еще не работал с функциями Composables в контексте этой навигационной библиотеки.

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

Когда я начинал, вариантов было не так уж много. Теперь же появилось достаточно ресурсов, готовых представить различные подходы к решению этой проблемы.

Среди множества существующих сейчас способов вы можете найти следующие.

  1. Создание интерфейса навигатора/маршрутизатора и реализация его в модуле приложения со всеми действиями и фрагментами.
  2. Загрузка фрагментов по квалифицированному имени (отражение) и использование API FragmentTransaction как обычно. Этот подход взят из презентации Бена Шваба (AirbnbEng).
  3. Использование навигационной библиотеки Jetpack.

Навигационная библиотека Jetpack

Одна из самых блестящих библиотек из группы Jetpack. Она позволяет объявлять весь граф приложения в одном файле и передавать надежные аргументы с помощью плагина. Jetpack предоставляет несколько навигационных графов, условную навигацию, диплинк-поддержку и т. д.

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

Не все золото, что блестит.

  1. По умолчанию библиотека НЕ поддерживает добавление фрагмента в стеке переходов назад. Чтобы эта опция стала возможной, вам придется последовать этому официальному совету. Примечание: недавно была выпущена новая версия, в которой предусматривалось добавление фрагментов по умолчанию, но этого так и НЕ не удалось добиться. Обнаружился баг компилятора. Он уже зарегистрирован и должен быть исправлен в следующей альфа-версии.
  2. Иногда возникают проблемы с панелью инструментов. К примеру, на ней может появиться обратная стрелка, даже если вы ее не активировали. Как и в случае с первым пунктом, вам придется прибегнуть к некоторым манипуляциям, чтобы отключить ее.
  3. Вы будете вынуждены изучить новые атрибуты XML, чтобы заставить их вести себя так, как вам нужно, когда пользователь делает возврат или выполняет любое другое действие.
  4. Если вы используете BottomNavigationView, то должны закрепить его видимость, реализовав листенеры и содержимое. Да, по умолчанию этот инструмент тоже не поддерживается.

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

Бесконечная головная боль

Как только вы решите перечисленные мной проблемы (да, все в этой жизни когда-нибудь решается), а также, вероятно, справитесь с другими трудностями, о которых я не упоминал, то с облегчением подумаете:

“Наконец-то, теперь я могу в полной мере насладиться потенциалом этой библиотеки”.

Что ж… Не уверен, подходит ли здесь слово “насладиться”, но использовать “полный потенциал” библиотеки вы сможете. Вот процессы, которые вам придется освоить во время работы с навигационной библиотекой.

  • Настройка файла res/navigation/navigation.xml на каждом функциональном модуле.
  • Передача аргументов “безопасным способом” через библиотеку путем добавления плагина safeArgs в функциональные модули, которые в нем нуждаются.
  • Включение всех id графов в файл модуля приложения navigation.xml. Это может быть непросто (будьте осторожны при смешивании идентификаторов графов).
  • Поддержка нескольких XML-файлов (в том случае, если вам будет недостаточно одного управления представлениями XML).
  • Навигация между функциональными модулями посредством диплинка. Вы можете передать идентификаторы через URL-адрес диплинка. Но что, если вам нужно будет отправить полный разделяемый/сериализуемый объект в конечный пункт назначения? Как это сделать с помощью диплинка? На данный момент я не нашел решения для этого варианта использования.

Да, вы открываете одну дверь, и еще 10 закрываются. Разве смысл библиотеки не в том, чтобы облегчить задачу? Однако после использования навигации библиотеки Jetpack я понял, что она не решает этих проблем.

Подход специалистов Airbnb с 2018 года

Я указываю год, так как, вероятно, способ навигации там уже изменился. Теперь мы видим, что composables заменяют целые фрагменты/действия, поэтому данный подход бесполезен в этих случаях.

Бен Шваб рассказывает о модуляризации

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

Однако в рамках этой статьи мы сосредоточимся только на навигации. По мнению Бена, следует полагаться на отражение и загружать fragments, activities, services и т. д. по их полному квалифицированному имени. Как только у вас появляется класс, вы можете вызвать менеджер по фрагментам и самостоятельно выполнить некоторые транзакции фрагментов/запустить интент, службы, широковещательные приемники (в принципе, вы можете загрузить любой класс).

Таким образом, вы получаете полный контроль над навигацией приложения. При этом вы используете только один класс. Никаких дополнительных плагинов или xml-файлов, ничего лишнего.

Я занимаюсь этим подходом около двух лет, и он действительно работает. До сих пор у меня не возникало никаких проблем с ним. Чтобы облегчить работу с транзакциями, я добавил некоторые расширения поверх API FragmentTransaction.

Вот для чего, собственно, и создаются библиотеки. Они должны упрощать программирование и избавлять вас от ненужной работы, а не прибавлять новые проблемы.

ChristopherME/movies-android
This library is built as an abstraction of the FragmentTransaction API. It doesn’t add any extra functions, just some…

github.com

Ниже я показываю, как я осуществляю навигацию (в том случае, если нахожусь в одном и том же функциональном модуле).

ActorsListFragment к ActorDetailFragment. Демонстрация навигационной библиотеки

Если мне нужно перейти от одного функционального модуля к другому, я использую помощник loadFragment от Бена, а также мой инструмент navigationExtensions(если это необходимо).

ActorDeailFragment открывает нижний лист, находящийся за пределами функционального модуля (используется подход loadFragment от Airbnb)

Вот и все. Да, использование loadFragment тоже вызывает определенные трудности. Я и не пытаюсь убедить вас в том, что этот подход идеален. Вы должны корректно прописать полное квалифицированное имя класса. К тому же, вам нужно будет добавить некоторые правила proguard, чтобы не запутаться в именах классов.

Но только и всего. Мне легче позаботиться об этих двух моментах, и навигационная библиотека Jetpack как раз и ограничивается этими двумя “сюрпризами”. Как я уже говорил, я использую метод “расширения для навигации + функция loadFragment” при работе с производственными приложениями, которые имеют несколько модулей, и у меня нет с этим никаких проблем.

Структура проекта из приложения, развернутого в галерее приложений Huawei с помощью библиотеки навигации, которую я прикрепил ранее

Финальные мысли

Я думаю, что библиотеке Jetpack еще предстоит пройти долгий путь. Она точно не предназначена для модульных проектов. Иногда мне кажется, что было бы намного лучше просто усовершенствовать API FragmentTransactions или предоставить больше расширений для работы с ним.

Как я уже говорил в начале этой статьи, я не пытаюсь нахваливать навигационную библиотеку Jetpack или отговаривать вас от ее использования. Я просто делюсь с вами собственным опытом.

Если вам кажется, что система не совсем подходит вам, корректируйте ее. Не бойтесь вносить изменения, но делайте это осторожно.

Если вы хотите увидеть, как совершить переход от Jetpack и использовать только расширения API FragmentTransactions, ознакомьтесь с этим запросом на включение (здесь также рассказывается о загрузке классов по квалифицированным именам).

Продолжу ли я пользоваться навигационной библиотекой Jetpack? Вероятно, да, но только тогда, когда в ней будет меньше багов и она станет более стабильной.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Christopher Elias: Honest thoughts on Jetpack Navigation library in modularized projects

Предыдущая статьяРеляционные базы данных в контейнерах Docker Compose
Следующая статьяПочему не стоит использовать or для проверки нескольких условий в Python