Привет, меня зовут Никита Чернобрисов, и я делаю Android-приложения в Doubletapp. Полтора года назад мы начали работать над приложением «Яндекс Путешествий» — само приложение доступно в Play Store, а про кейс подробно можно прочитать тут. При старте у нас возникло много архитектурных холиваров, в частности о том, как хранить ресурсы и пользоваться ими. И, как это и заведено, первые решения оказались неудачными. Я расскажу вам, дорогие читатели, удары каких граблей оставили больше следов и к чему мы пришли. Статья будет полезна тем, кто начинает работу над многомодульным проектом или уже во время разработки столкнулся с такими проблемами: любое минорное изменение в ресурсах вызывает пересборку всего проекта, приходится постоянно ходить в чат к команде и спрашивать, добавлял ли кто-то нужную тебе иконку, дубликаты ресурсов, будь то копия в одном модуле или в разных, сложно понять, есть ли в проекте нужный стиль, так как их уже сотня, проще добавить новый, все равно никто не заметит, вам просто что-то не нравится в том, как у вас хранятся ресурсы, и вы хотите это улучшить :) Дисклеймер! Рассказ будет включать как оптимальные решения, так и откровенно плохие в том хронологическом порядке, в каком они появлялись у нас на проекте. Поэтому, если у вас есть желание позаимствовать опыт, то рекомендую либо дочитать статью до конца, либо проскроллить вниз и посмотреть, к чему мы пришли в итоге. Для статьи был подготовлен небольшой дизайн-проект в фигме, а также Android-проект со структурой брендбука. Фигма тут. Репозиторий с проектом тут. Итак, к нам пришел «заказчик / менеджер / кто-то, дающий нам задачи» и сказал, что надо разработать приложение с десятком разных фич, а какие-то отдельные фичи мы хотим в будущем встраивать в другие наши проекты. Дизайнеры уже набросали макеты приоритетных фичей. Мы как осознанные разработчики не ленимся и сразу начинаем придумывать многомодульную архитектуру, дабы в будущем и нам, и потомкам было проще с этой махиной работать. Мы набросали, по какому контракту будут взаимодействовать модули между собой, как будут ходить запросы в сеть и тому подобное, и в конце концов пришли к вопросу, что мы делаем с нашими ресурсами — иконками, картинками, цветами, текстами. Для удобства словосочетание «устройство хранения ресурсов приложения» мы в команде называем «брендбуком», так же будем и в статье. Теперь решаем, какие цели должен выполнять брендбук: каждый модуль приложения имеет возможность использовать все необходимые ему ресурсы, редактирование брендбука не вызывает долгую пересборку большей части проекта, каждый ресурс хранится в единственном экземпляре, и у нас нет сомнений, что ресурс, использованный в макете, присутствует в проекте, нам не нужно постоянно создавать новые стили, если на такой шаг не пошли дизайнеры. Прежде чем начать думать над решением, проверим наши макеты. Сейчас мы смотрим не на то, учтены ли все пограничные случаи работы фичей, а на то, вынесены ли все стили и ресурсы в отдельные компоненты. Важно, чтобы все макеты строились на определенном наборе стилей и компонентов — такие ограничения улучшат дизайн, потому что он будет строго регламентирован набором элементов и правил по их построению. Итак, два скриншота: на первом — фигма курильщика, на втором — дизайнера. В первом варианте мы видим, что дизайнер не подумал, какие типы текстов будут у нас в приложении, и нарисовал на глаз. Такой подход приведет к потере консистентности оформления текста в разных частях приложения, и нам как разработчикам придется постоянно проверять каждый параметр текста при верстке. Мы этого не хотим и просим привести фигму в порядок, чтобы она выглядела, как на втором скриншоте: красота, мы видим имена стиля и цвета и при верстке задаем их, ведь они уже будут лежать у нас в проекте (про то, как и куда класть — читать дальше). Это правило работает не только для текстов, но и для цветов, иконок и иллюстраций — просим дизайнера провести такую работу. А далее начинается следующий этап приготовлений в фигме. Теперь фигма у нас систематизирована и шанс, что неожиданно появятся новые стили без нашего ведома, снижен. Мы можем начать добавлять ресурсы в проект, но перспектива просматривать каждый новый экран в поисках не добавленных в общие компонентов звучит нерадужно, хотелось бы все собрать в одном месте. Это мы и попросим сделать наших дизайнеров — отдельная страница в фигме с описанием всех компонентов и стилей. Выглядеть это должно примерно так: Фрейм colors включает в себя перечисление всех цветов (кстати, значения некоторых цветов могут дублироваться и это нормально: дизайн-команда сможет в будущем изменить цвет группы элементов, не задевая другие), используемых в дизайне, typography — стили текста, illustrations — сложные картинки, которые мы не сможем конвертировать из svg в vector drawable, icons — иконки, векторы которых, напротив, прекрасно конвертируются студией, и components — более сложные компоненты, например, кнопки или элементы, которые будут у нас представлены как кастомные вьюшки. Также здесь могут быть перечислены другие описания интерфейса, например, скорость анимаций, цвета, углы градиентов и тому подобное. На этом приготовления в фигме окончены, и мы наконец-то можем пойти в студию собирать наш брендбук уже в проекте. Как уже говорилось, элементы брендбука должны быть доступны всем модулям, которые работают с отображением интерфейса. Разумеется, мы приходим к тому, что брендбук будет отдельным модулем, и он в дальнейшем будет импортироваться в другие модули. Добавляем в проект цвета, иконки, иллюстрации в разных размерах, делаем кастомные вьюшки для кнопок. Для удобства в дальнейшем сопоставлении цветов в фигме и в проекте стоит сохранить те нейминги, которые дали дизайнеры — так не придется каждый раз вспоминать, под каким именем у нас хранится та или иная иконка. Я бы рекомендовал только добавить уникальный для проекта префикс или суффикс в название. Это спасет нас от ситуации, когда нужно будет подключить отдельную фичу в другой проект, но там уже будет храниться ресурс с таким же именем (в любом проекте найдется иконка с названием ic_arrow или цвет с именем background). Так будет выглядеть объявление цветов приложения: Такой метод хранения ресурсов малозатратный в плане времени на реализацию и на первых этапах проекта будет прекрасно работать. Если есть стопроцентная уверенность в том, что проект никогда не вырастет из 3-4 модулей, то тут можно и остановиться. Но я уверенно скажу, что кто-то из разработчиков повесится, когда у вас будет 100+ модулей, ведь сборка проекта будет занимать время, сравнимое с продолжительностью полета к альфе Центавра. Давайте разберемся почему. Нарисуем схему зависимостей небольшого проекта, состоящего из 6 модулей: Как мы видим, брендбук подключается в каждую из фичей, а система сборки работает так, что при изменении модуля запустится не только сборка его самого, но и всех модулей, которые от него зависят. На выходе мы получим картину, когда измененный цвет кнопки запустит цепочку пересборок практически по всему проекту. Хоть красить кнопки и наша главная задача, столько ждать мы не готовы, думаем дальше. Наш брендбук оказался слишком спросовым. Решение будет состоять в том, чтобы поделить его на несколько, но как? За время наших страданий с монолитом мы пришли к весьма логичным и элементарным выводам, хоть и не очевидным в начале разработки. Во-первых, такие константы, как цвета или иконки, меняются крайне редко, при этом каждый из экранов их использует. Во-вторых, намного чаще изменения происходят в более сложных кастомных вьюшках. Это могут быть целые формы для заполнения с различными условиями для отображения тех или иных полей. При этом не все экраны требуют каждую добавленную в брендбук вьюшку. Отталкиваясь от этого, делаем простой вывод насчет дробления брендбука: самые спросовые и редко меняющиеся элементы оставляем в текущем модуле, а сложные вьюшки выносим в собственные. На выходе мы получим примерно такую схему зависимостей: Таким образом, количество пересборок станет намного меньше. Да, при добавлении новой иконки у нас все еще будут заново собираться модули всех фичей, но это не будет происходить в каждом тикете. А вот при условном изменении цвета текста в нашем кастомном тулбаре пересоберется только одна фича. Такой структура брендбука и останется, а мы перейдем к автоматизации экспорта ресурсов из фигмы. Добавим еще один элемент тюнинга брендбука — автоэкспорт ресурсов. Для этого воспользуемся утилитой FigmaExport. Мы кратко пробежимся по алгоритму ее подключения в наш проект. Стоит сразу оговориться: утилита использует FigmaApi, который требует вынесения скачиваемых компонентов в библиотеки. Функционал экспорта платный, поэтому ваша команда должна иметь оплаченную подписку.   Мы не просто так просили коллег-дизайнеров создавать отдельные компоненты для цветов, текстов и изображений и размещать их на выделенных фреймах. Это необходимое условие для работы FigmaExport. Если эта работа была проделана, то остался последний шаг — публикация компонентов в командную библиотеку, общее хранилище стилей, чтобы предоставить к ним доступ извне. Для этого щелкаем на каждый из фреймов правой кнопкой и жмем Publish selected components. Готово, идем в студию! Оформим эту часть, как инструкцию с шагами: Cкачиваем последнюю версию исходников отсюда. Достаем содержимое архива и кладем прямо в наш модуль core/brandbook рядом с директорией src, структура модуля будет выглядеть так: Опционально можно удалить папку figma-export_XcodeExport.bundle, она нам не потребуется. На скрине можно заметить файл figma-export.yaml — это конфигурация для запуска экспорта, его нам нужно создать самим. Готовый пример файла можно найти тут, а полный перечень параметров — тут. Мы разберем минимальную рабочую версию и посмотрим основные параметры файла   figma-export.yaml lightFileId — id страницы в фигме, его можно достать из url. Открываем фигму и копируем текст после file и до названия проекта. Если приложение поддерживает темную тему, то используем darkFileId и сделаем то же самое; nameValidateRegexp — регулярное выражение, валидирующее имя ресурса, если в названии будет несовпадение с регуляркой, то мы об этом узнаем в терминале; nameReplaceRegexp — как я писал выше, мы хотим добавлять префикс в именования ресурсов в проекте, тут мы и используем этот параметр. Укажем fifty_two_challenge_app_$1_color и text_primary превратится в fifty_two_challenge_app_text_primary_color; В секции android мы прописываем, где находится директория для хранения ресурсов mainRes, далее для иконок и иллюстраций мы указываем output — директории, куда их сохранять относительно mainRes. Отдельно для иллюстраций задаем такие параметры, как format — формат, в котором должен происходить экспорт, например webp*,* и требуемые размеры scales, которые могут принимать значения: 1 (mdpi), 1.5 (hdpi), 2 (xhdpi), 3 (xxhdpi), 4 (xxxhdpi). Осталось добавить наш фигма-токен, чтобы FigmaExport получил доступ к макетам. Для этого идем в настройки профиля figma, находим пункт Personal access tokens, копируем и добавляем в переменные среды командой: export FIGMA_PERSONAL_TOKEN=[место_для_вашего_токена]  Поехали! Для запуска выполняем команды в терминале, и ресурсы из фигмы прилетят прямо в проект! Метод хранения ресурсов обычно обсуждается не так активно при создании архитектуры многомодульного приложения, но его неправильный выбор может привести к большим проблемам как в техническом плане, так и в элементарном удобстве при верстке новых экранов. После проделанной работы мы сильно ускорили время сборки проекта и получили удобную структуризацию всех ресурсов приложения. Из минусов я бы выделил только то, что теперь для каждой отдельной вьюшки приходится тратить время на создание дополнительного модуля, но оно не сравнится с тем, которое уходило бы на пересборки. По мотивам приключений нашей команды я бы выделил такие тезисы: всегда следите за тем, чтобы модуль с основными ресурсами не менялся слишком часто, так как он вызывает пересборку большей части проекта; если какой-то компонент изменяется слишком часто, но не используется во всем приложении, то его стоит вынести в отдельный модуль; если у вас нет автоэкспорта, обговорите в команде, кто, как и когда добавляет новые ресурсы, как вы будете именовать ресурсы, где вы будете их хранить. Увидев какой-то компонент в фигме, разработчик не должен тратить 10 минут на попытки понять, есть ли он уже в проекте или нет; удобная фигма — ответственность не только дизайнера, все предлагайте свои улучшения — коллеги это оценят. «Яндекс Путешествия» в портфолио Doubletapp. SwiftUI для большого B2C продукта – доклад iOS-разработчицы Полины Скалкиной. «Внешняя навигация» в многомодульном проекте на Kotlin.