Автоматичне генерування ресурсів із Figma для Compose

💡 Усі статті, обговорення, новини про Mobile — в одному місці. Приєднуйтесь до Mobile спільноти!

Вітаю! Мене звати Володимир, я Android-розробник. Нам часто потрібно автоматизовувати процеси, що можуть бути рутиною та займати багато часу, якщо все робити вручну. На попередньому проєкті ми мали White-Label-рішення, що брало за основу одну кодову базу та містило різні UI-кастомізації для клієнтів.

В цьому пості ми розглянемо різні підходи та інструменти, які допомогли нам відразу перейти до написання коду, не витрачаючи час на пошук та правку змін від дизайнерів.

FIGMA WHATEVER DSM

Почнемо з простого. Дизайнери, як і програмісти, також намагаються в DRY та придумують для цього чудові інструменти, як Design System Manager. Спочатку визначаються компоненти, що можуть повторюватись в проєкті, і розробляється структурований набір кольорів та стилів. Тому якщо будь-який колір чи стиль зміниться в одному місці, він автоматично змінюється по всьому проєкту. Зручно, чи не так? У Figma чи будь-якому іншому сервісі є можливість експортувати такі конфігурації в спеціальний файл, який ми потім будемо аналізувати та генерувати на його основі код.

Якщо ви працюєте з Jetpack Compose, то можете пригадати, що є такі класи як ColorScheme та TextStyle. З філософією Material у ColorScheme ми описуємо кольори, а TextStyle описує вигляд тексту, що використовуються компонентами для рендеру.

Кодогенерація

Якщо ви використовували такі бібліотеки як Room, Glide або будь-які інші, що потребували кодогенерації, ця схема вже трішки для вас знайома.

В Java можна зустріти назву anation processing, в Kotlin — kapt чи ksp. Загалом, це — функція компілятора, яка допомагає нам проаналізувати код та згенерувати додаткові файли. Але оскільки ми не можемо на json-файли повісити анотації, і це не є kotlin-файл, тут потрібне інше рішення. Тож ми з вами навчимося розробляти плагін, що приймає на вхід дані, шукає потрібний файл та генерує необхідні класи.

Gradle Plugin

Існує багато варіантів того, як зробити gradle plugin, починаючи від написання задач в головних gradle-файлах до розміщення в buildSrc, тому скористаємось рекомендаціями, а саме — Composite Builds.

Суть проста — плагіни лежать окремо, код застосунку — окремо, потім це все залишається поєднати. Тут можна переглянути дуже багато прикладів того, як AGP використовує цей механізм. Та і взагалі рекомендую ознайомитися, можливо знайдете відповідь на питання, яке вас давно хвилювало.

Ось так буде виглядати структура модуля, в якому буде розроблятися плагін:

Нічого екстраординарного тут нема, різниця тільки в тому як він підключається на найвищому рівні — завдяки інструкції includeBuild() :

В build.gradle.kts ми маємо описати залежності, які потрібні для роботи плагіну, та деякі базові налаштування:

Gradle Plugin Implementation

Отож, що ми маємо зробити?

  1. Проаналізувати структуру DSM-токенів та підготувати для цього відповідні моделі.
  2. Знайти всі можливі флейвори, в яких лежать токени, що описують дизайн кожного клієнта в проєкті.
  3. Якимось чином проаналізувати ці дані та навчитися генерувати потрібні файли.
  4. Зробити зручно і це обернути в gradle-таски?
  5. ...
  6. Писати код!

Як ми бачимо, у файлі є опис кольорів і вони каскадно можуть складатися з кількох логічних рівнів, де останній містить інформацію, що нас цікавить. Зазвичай ресурси мають подібну структуру, де вказується їх тип та значення.

{
  "color": {
    "m3": {
      "white": {
        "description": "",
        "type": "color",
        "value": "#ffffffff",
        "blendMode": "normal"
      },
      "black": {...},
      "sys": {
        "light": {...},
        "dark": {...}
      },
      "ref": {...},
      "key-colors": {...},
      "source": {...},
      "surfaces": {...},
      "state-layers": {...}
    }
  },
  "font": {...},
  "typography": {...}
}

Стосовно кольору нас цікавить значення, тому відповідний клас буде містити його hex-представлення.

Gradle tasks

Тут трішки варто розказати про їх створення. Якщо вам потрібна гнучкість у розробці, зазвичай ви будете програмно описувати задачі за схожою структурою.

При реєстрації ми маємо заповнити всі поля, що позначені анотаціями, а саме — шлях до json-файлу, шлях до теки нашого флейвору та фінальний package згенерованого файлу.

Для полегшення собі життя ми деякі речі захардкодимо, але якщо потрібна більша гнучкість — варто глянути в сторону gradle extensions.

Оскільки ця задача є абстрактною, деякі конфігурації ми залишаємо за реалізацією, тому вигляд для задачі, що генеруватиме файл з кольорами, буде таким:

Щоб наші задачі можна було запустити, їх варто зареєструвати в проєкті.

Для цього нам спочатку потрібно за допомогою плагіну визначити, які є build variants у проєкті, та зареєструвати задачу кожного типу.

А тепер перейдімо до найцікавішого — як же відбувається генерування файлів?

Для цього нам потрібно використати такий інструмент, як KotlinPoet. Він дозволить доволі просто створювати файли, що нам потрібні. Весь механізм складається із різного роду білдерів, якими ми додаємо типи файлів, їхні властивості, значення тощо.

Вся суть нашої задачі полягає в тому, щоб розпарсити json, отримати потрібні дані та записати у відповідні файли.

Оскільки ми знайомі зі структурою json-файлу, то можемо написати функцію, яка буде ходити по ньому та збирати дані. В кінцевому результаті це буде мапа з ключами-назвами полів та значеннями-модельками.

Коли ж ми отримали список всіх полів, використовуючи KotlinPoet описуємо, що бажаємо створити файл, який міститиме object з полями, що було зчитано. В кінцевому результаті в нас мають вийти файли такого вигляду:

Використовуючи згенеровані файли ми можемо описати нашу тему.

За потреби ми можемо використовувати стилі та кольори напряму.

От і все, наше початкове завдання виконано. Тепер ми маємо змогу завантажувати файл до проєкту, запускати задачу й отримувати файли, що вже можна використати у розробці.

Те, що раніше робилося вручну і могло містити помилку через людський фактор, наразі працює як годинник та не потребує додаткових залежностей (поточне iOS та попереднє Android-рішення вимагало встановлення тонни Node.JS-модулів, щоб запустити скрипт, який до кінця не виконував свої задачі та все одно потребував правок розробника).

Неочевидні проблеми

Розробити плагін — це пів біди. У нас завжди буде щось, що піде не так.

  1. Через складність проєкту ми дизайнери не використовували Material правильно і розробили десятки, якщо не сотні власноіменованих кольорів та стилів. Через це використання звичайного MaterialTheme не було можливим, а наш плагін генерував кастомні ColorScheme та Typography-класи.
  2. Відносно багато часу пішло на початкову розробку та міграцію, проте, якщо у вас чистий Material і не треба з дизайнерами сидіти годинами для розв’язання питань іменування токенів, це не має бути проблемою.
  3. Всі @Preview треба було огортати в нашу тему, оскільки вона ламалась під час побудови.
  4. Проблемою можуть бути власне дизайни — один з клієнтів мав кнопку з градієнтом, хоча конкретно для цього токену мав бути колір. Все вирішилося елегантним костилем рішенням, але ми дуже попросили дизайнерів уникати таких конфліктів в майбутньому.
  5. Складність конфігурації — сам процес хоча і straight-forward, такі косяки, як попередній, може потребувати солідної зміни логіки роботи плагіну чи структури моделей.

А на цьому все. Напишіть в коментарях, які ви будували подібні рішення або ж якщо знаєте, як не писати власну тему кожного разу для перегляду Preview. Якщо комусь потрібен код, то весь проєкт лежить тут.

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

у нас флатер і ми пішли простіше. я просто вимагаю від людини яка готує фігму нарізати сторінку де прописані мена і хекси кольорів, типографія, стилі загальних елементів (діалоги, кнопки). по цій сторінці я один раз конфігурую тему апки. також обумовлено, шо якшо шось у фігмі не використовує колір по імені, описану типографію — воно так і залітає в апку в сирому видіі, відповідно, не має ніякої підтримки теми. особливо круто коли дизайнер вже ± знайомий з матіріалом і може сам зібрати всякі onPrimary, headlineLarge і тд

Доволі круто звучить, якщо є можливість так організувати роботу і цим дизайнери спрощують життя розробникам. Нам так не повезло, оскільки цілий дизайн департмент не піддавався на шантаж мобайл (і не тільки) команд і вирішив зробити по-тупому, де в них є component-driven токени, а-ля, якщо бачиш тулбар, то в тебе буде Theme.colors.toolbarBackground і тому подібне.

навіть просто зібрати всякі Theme.colors.toolbarBackground в одному місці вже спрощує розробку. принаймні ти бачиш це все в одному місці і можеш прикинути як тобі зібрати це все в стилі і натягнути на андроід/іос/флутер. дизайнери, як митці, не розуміють концепту того, що фігма це частина технічного завдання і має бути структурована і мати якусь версійність. тому крутимось як можемо :(
був ще цікавим досвід зі скетчем. ми брали скетч файли з зафіскованим дизайном для скоупа на спрінт і підливали їх до спеціальної сторі. трохи вийшло дисциплінувати UI/UX тіму, тому що всі їх доробки дизайну йшли виключно як CR через повний цикл апрувів. займало час — так, але згодом сіарів почало ставати менше.
також гарна практика, за наявності на це часу, робити дизайн рев"ю частини UI як йде в розробки до старту розробки на відповідність сторям. бо зазвичай у нас під кінець проекту накопичувалась купа багів де дизайнери шось мінорне мовчки підправляють (текстовки, віддітнки кольорів, відступи) і це виливалось в зайвий час зі сторони QA команди і в горящі сраки у девелоперів.

Це якщо свідомі дизайнери є, то це прекрасно, бо на нас клали болт, переважно давали якісь огризки і ми крутилися вже як могли, бо в них веб був більш престижним.

Мені здається, що на початку саме цю лібу і засунули в проект, щоб просто файл кольорів писати. З текстовими стилями там була історія погірше і щось точно що робити вручну. Ну і та — тему автоматично воно не генерувало.

Ну там є можливість писати кастомні трансформери. Я якраз такий писав для Flutter. Впринципі doable

І доречі, була б дуже цікаво побачити як ця таска інтегрується в СІ і як ви автоматизували процес підтягувати згенерованих змін в проєкт?

Ніяк, це чисто мануальні таски, щоб унеможливити будь-які зміни коду поза IDE.
Суть була в тому, щоб піти від 3rd-party компонентів і генерувати ці файли, як тільки дизайнери релізять нову версію токенів (в нас для цього був скрипт).
А до того був якийсь кошмар із кривих скриптів, що запускалися через ноду, а на вінді пацани взагалі деякі ламалися, коли треба було таке поставити.
+ не всі шарили, як писати JS, якщо іноді треба щось змінити.

І код, як можете глянути в проекті, писався в flavor, щоб було легше бачити, якщо щось витерлося чи де змінилося у файлах при котімі.

Я колись робив так. Дизайнери використовують Figma plugin для Design Tokens. Там є можливість зберігати ці токени в Github, коли дизайнери зберігають зміни. Я додав СІ конфігурацію, яка трекала такі зміни в repository і запускала джобу, що брала ці токени, генерували на основі низ Flutter library і пушила згенеровані ченджі в repository уже Fluter lib і паблішила нові версію. В результаті клінти цієї ліби отрмаували нотіфікейшен що є нова версії ліби і могли проапдейтити її вручні (я вирішив що апдейти треба робити вручну, щоб уникнути випадків коли дизайнери поламали токени).

Оце десь щось таке і нас було схоже, але через рукожопість адмінів все було дуже складно по інфраструктурі і часто десь та і щось треба було робити руками. Про ліби як такі також думали, наразі такий підхід і використовую, але там трохи було складно з флейворами, тому просто забили і клали ці файли напряму в репо.

Підписатись на коментарі