Pull to refresh
43.44

Храним краску с умом: как организовать ресурсы в многомодульном проекте

Level of difficultyEasy
Reading time9 min
Views4.2K

Привет, меня зовут Никита Чернобрисов, и я делаю Android-приложения в Doubletapp. Полтора года назад мы начали работать над приложением «Яндекс Путешествий» — само приложение доступно в Play Store, а про кейс подробно можно прочитать тут. При старте у нас возникло много архитектурных холиваров, в частности о том, как хранить ресурсы и пользоваться ими. И, как это и заведено, первые решения оказались неудачными. Я расскажу вам, дорогие читатели, удары каких граблей оставили больше следов и к чему мы пришли.

Для кого эта статья?

Статья будет полезна тем, кто начинает работу над многомодульным проектом или уже во время разработки столкнулся с такими проблемами:

  • любое минорное изменение в ресурсах вызывает пересборку всего проекта,

  • приходится постоянно ходить в чат к команде и спрашивать, добавлял ли кто-то нужную тебе иконку,

  • дубликаты ресурсов, будь то копия в одном модуле или в разных,

  • сложно понять, есть ли в проекте нужный стиль, так как их уже сотня, проще добавить новый, все равно никто не заметит,

  • вам просто что-то не нравится в том, как у вас хранятся ресурсы, и вы хотите это улучшить :)

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

Для статьи был подготовлен небольшой дизайн-проект в фигме, а также Android-проект со структурой брендбука.

Фигма тут.

Репозиторий с проектом тут.

Начинаем

Итак, к нам пришел «заказчик / менеджер / кто-то, дающий нам задачи» и сказал, что надо разработать приложение с десятком разных фич, а какие-то отдельные фичи мы хотим в будущем встраивать в другие наши проекты. Дизайнеры уже набросали макеты приоритетных фичей. Мы как осознанные разработчики не ленимся и сразу начинаем придумывать многомодульную архитектуру, дабы в будущем и нам, и потомкам было проще с этой махиной работать. Мы набросали, по какому контракту будут взаимодействовать модули между собой, как будут ходить запросы в сеть и тому подобное, и в конце концов пришли к вопросу, что мы делаем с нашими ресурсами — иконками, картинками, цветами, текстами.

Для удобства словосочетание «устройство хранения ресурсов приложения» мы в команде называем «брендбуком», так же будем и в статье. Теперь решаем, какие цели должен выполнять брендбук:

  • каждый модуль приложения имеет возможность использовать все необходимые ему ресурсы,

  • редактирование брендбука не вызывает долгую пересборку большей части проекта,

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

  • нам не нужно постоянно создавать новые стили, если на такой шаг не пошли дизайнеры.

Договор с дизайнерами

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

Итак, два скриншота: на первом — фигма курильщика, на втором — дизайнера.

Фигма курильщика: текст вставлен без привязки к стилю, мы видим все его параметры в виде размера, цвета и так далее
Фигма курильщика: текст вставлен без привязки к стилю, мы видим все его параметры в виде размера, цвета и так далее
Фигма дизайнера: к тексту применен вынесенный в компоненты стиль, нам важен не каждый его параметр, а имя стиля, касается и цвета
Фигма дизайнера: к тексту применен вынесенный в компоненты стиль, нам важен не каждый его параметр, а имя стиля; касается и цвета

В первом варианте мы видим, что дизайнер не подумал, какие типы текстов будут у нас в приложении, и нарисовал на глаз. Такой подход приведет к потере консистентности оформления текста в разных частях приложения, и нам как разработчикам придется постоянно проверять каждый параметр текста при верстке. Мы этого не хотим и просим привести фигму в порядок, чтобы она выглядела, как на втором скриншоте: красота, мы видим имена стиля и цвета и при верстке задаем их, ведь они уже будут лежать у нас в проекте (про то, как и куда класть — читать дальше).

Это правило работает не только для текстов, но и для цветов, иконок и иллюстраций — просим дизайнера провести такую работу. А далее начинается следующий этап приготовлений в фигме.

Дизайн-система (пригодная для дальнейшего автоэкспорта)

Теперь фигма у нас систематизирована и шанс, что неожиданно появятся новые стили без нашего ведома, снижен. Мы можем начать добавлять ресурсы в проект, но перспектива просматривать каждый новый экран в поисках не добавленных в общие компонентов звучит нерадужно, хотелось бы все собрать в одном месте. Это мы и попросим сделать наших дизайнеров — отдельная страница в фигме с описанием всех компонентов и стилей. Выглядеть это должно примерно так:

Как выглядит минимальная дизайн-система в фигме
Как выглядит минимальная дизайн-система в фигме

Фрейм colors включает в себя перечисление всех цветов (кстати, значения некоторых цветов могут дублироваться и это нормально: дизайн-команда сможет в будущем изменить цвет группы элементов, не задевая другие), используемых в дизайне, typography — стили текста, illustrations — сложные картинки, которые мы не сможем конвертировать из svg в vector drawable, icons — иконки, векторы которых, напротив, прекрасно конвертируются студией, и components — более сложные компоненты, например, кнопки или элементы, которые будут у нас представлены как кастомные вьюшки. Также здесь могут быть перечислены другие описания интерфейса, например, скорость анимаций, цвета, углы градиентов и тому подобное.

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

Первый вариант, который не стоит повторять

Как уже говорилось, элементы брендбука должны быть доступны всем модулям, которые работают с отображением интерфейса. Разумеется, мы приходим к тому, что брендбук будет отдельным модулем, и он в дальнейшем будет импортироваться в другие модули. Добавляем в проект цвета, иконки, иллюстрации в разных размерах, делаем кастомные вьюшки для кнопок. Для удобства в дальнейшем сопоставлении цветов в фигме и в проекте стоит сохранить те нейминги, которые дали дизайнеры — так не придется каждый раз вспоминать, под каким именем у нас хранится та или иная иконка. Я бы рекомендовал только добавить уникальный для проекта префикс или суффикс в название. Это спасет нас от ситуации, когда нужно будет подключить отдельную фичу в другой проект, но там уже будет храниться ресурс с таким же именем (в любом проекте найдется иконка с названием ic_arrow или цвет с именем background). Так будет выглядеть объявление цветов приложения:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color
    name="fifty_two_challenge_app_text_primary">#000000</color>
    <color
    name="fifty_two_challenge_app_text_inverse">#FFFFFF</color>
    <color
    name="fifty_two_challenge_app_text_control_secondary">#747474</color>
    <color
    name="fifty_two_challenge_app_control">#71DE9D</color>
    <color
    name="fifty_two_challenge_app_background_main">#D6DED9</color>
    <color
    name="fifty_two_challenge_app_background_secondary">#FFFFFF</color>
    <color
    name="fifty_two_challenge_app_control_pressed">#4FC982</color>
    <color
    name="fifty_two_challenge_app_control_disabled">#AFAFAF</color>
    <color
    name="fifty_two_challenge_app_control_highlighted">#F3F3F1</color>
</resources>

Такой метод хранения ресурсов малозатратный в плане времени на реализацию и на первых этапах проекта будет прекрасно работать. Если есть стопроцентная уверенность в том, что проект никогда не вырастет из 3-4 модулей, то тут можно и остановиться. Но я уверенно скажу, что кто-то из разработчиков повесится, когда у вас будет 100+ модулей, ведь сборка проекта будет занимать время, сравнимое с продолжительностью полета к альфе Центавра. Давайте разберемся почему. Нарисуем схему зависимостей небольшого проекта, состоящего из 6 модулей:

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

Дробим наш монолит

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

  • Во-первых, такие константы, как цвета или иконки, меняются крайне редко, при этом каждый из экранов их использует.

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

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

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

Figma автоэкспорт

Добавим еще один элемент тюнинга брендбука — автоэкспорт ресурсов. Для этого воспользуемся утилитой FigmaExport. Мы кратко пробежимся по алгоритму ее подключения в наш проект. Стоит сразу оговориться: утилита использует FigmaApi, который требует вынесения скачиваемых компонентов в библиотеки. Функционал экспорта платный, поэтому ваша команда должна иметь оплаченную подписку.

Подготовка в Figma

Мы не просто так просили коллег-дизайнеров создавать отдельные компоненты для цветов, текстов и изображений и размещать их на выделенных фреймах. Это необходимое условие для работы FigmaExport. Если эта работа была проделана, то остался последний шаг — публикация компонентов в командную библиотеку, общее хранилище стилей, чтобы предоставить к ним доступ извне. Для этого щелкаем на каждый из фреймов правой кнопкой и жмем Publish selected components. Готово, идем в студию!

Подключаем FigmaExport в проект

Оформим эту часть, как инструкцию с шагами:

  1. Cкачиваем последнюю версию исходников отсюда.

  2. Достаем содержимое архива и кладем прямо в наш модуль core/brandbook рядом с директорией src, структура модуля будет выглядеть так:

Untitled
  1. Опционально можно удалить папку figma-export_XcodeExport.bundle, она нам не потребуется.

  2. На скрине можно заметить файл figma-export.yaml — это конфигурация для запуска экспорта, его нам нужно создать самим. Готовый пример файла можно найти тут, а полный перечень параметров — тут. Мы разберем минимальную рабочую версию и посмотрим основные параметры файла

    • figma-export.yaml

      figma:
        lightFileId: 75cKsK1wB2obpck3TfevLb
      common:
      colors:
      nameValidateRegexp: '^([a-zA-Z_]+)1_color'
      typography:
      nameValidateRegexp: '^([a-zA-Z_]+)1_style'
      images:
      nameValidateRegexp: '^([a-zA-Z_]+)1'
      icons:
      nameValidateRegexp: '^([a-zA-Z_]+)1'
      android:
      mainRes: './src/main/res'
      icons:
      figmaFrameName: Icons
      output: "icons"
      images:
      figmaFrameName: Illustrations
      format: webp
      output: "images"
      scales: [1, 1.5, 2, 3, 4]
      webpOptions:
      encoding: lossy
      quality: 90
      

    lightFileId — id страницы в фигме, его можно достать из url. Открываем фигму и копируем текст после file и до названия проекта. Если приложение поддерживает темную тему, то используем darkFileId и сделаем то же самое;

Untitled

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).

  1. Осталось добавить наш фигма-токен, чтобы FigmaExport получил доступ к макетам. Для этого идем в настройки профиля figma, находим пункт Personal access tokens, копируем и добавляем в переменные среды командой:

    export FIGMA_PERSONAL_TOKEN=[место_для_вашего_токена]

  2. Поехали! Для запуска выполняем команды в терминале, и ресурсы из фигмы прилетят прямо в проект!

    # Экспорт иконок
    ./figma-export/Release/figma-export icons -i figma-export.yaml
    
    # Экспорт изображений
    ./figma-export/Release/figma-export images -i figma-export.yaml
    
    # Экпорт стилей текста
    ./figma-export/Release/figma-export typography -i figma-export.yaml
    
    # Экспорт цветов
    ./figma-export/Release/figma-export colors -i figma-export.yaml
    

Подводим итоги

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

По мотивам приключений нашей команды я бы выделил такие тезисы:

  • всегда следите за тем, чтобы модуль с основными ресурсами не менялся слишком часто, так как он вызывает пересборку большей части проекта;

  • если какой-то компонент изменяется слишком часто, но не используется во всем приложении, то его стоит вынести в отдельный модуль;

  • если у вас нет автоэкспорта, обговорите в команде, кто, как и когда добавляет новые ресурсы, как вы будете именовать ресурсы, где вы будете их хранить. Увидев какой-то компонент в фигме, разработчик не должен тратить 10 минут на попытки понять, есть ли он уже в проекте или нет;

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

Еще о проекте:

Tags:
Hubs:
Total votes 3: ↑3 and ↓0+3
Comments12

Articles

Information

Website
doubletapp.ai
Registered
Founded
Employees
51–100 employees
Location
Россия