Всем привет!

Меня зовут Михаил. Я Android-разработчик в продуктовой команде Циан.

При разработке новой фичи приходится тратить время на создание однотипных файлов, в которых мы будем писать логику. Такие файлы очень похожи друг на друга, зачастую различие только в названии. Будь то создание presentation-слоя c Fragment -> ViewModel -> ComposeWidget (с типовыми State) или data-слоя c интерфейсами, Repository и их реализацией или создание компонента для DI.

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

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

Размышляя, в каком виде я хочу видеть этот процесс, я пришёл к таким критериям:

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

  • Шаблон должен легко модифицироваться всей командой разработки.

  • Шаблоны не надо искать или скачивать. При свежем develop – свежие шаблоны.

  • Это точно не скрипт, так как сложно поддерживать его актуальность.

  • Все должно работать в IDE и легко устанавливаться.

В далёком 2020-м году, Google удалили из AS поддержку кастомных Freemarker-ных шаблонов, альтернатив которым особо не было. А то, что имелось, не подходило моим критериям.  

Совместив все критерии, я пришел к выводу, что мне нужен мой собственный plugin для IDE, который полностью удовлетворит все мои потребности.

И я его, знаете-ли, сделал..

Group File Template (GFT) — это плагин для создания классов из подготовленных заранее шаблонов. Настолько гибко, что подойдёт не только Android-разработчику.

Где его найти? В Marketplace IDEA или Android Studio. Просто вбиваем в поиск «GFT». Он там такой один.

Как работает плагин?

Шаблоны для использования должны находиться в корне вашего проекта в каталоге templates.

Файлы с расширением .tm  — это абстрактный шаблон класса. На их основе и будут генерироваться классы. За генерацию по этим шаблонам отвечает «main.json».

В шаблон можно добавлять свои параметры, которые будут заполняться из IDE.

Main поддерживает 2 типа параметров: 

  • поле для заполнения, в которое можно ввести любой текст;

  • выпадающий список, в котором надо будет выбрать один из вариантов.

Плагин создаёт файлы в той папке, из которой было вызвано создание.

Поближе рассмотрим main.json. Он имеет следующую структуру:

{
  "name": "TestTemplate", // Название Шаблона
  "description": "Example for documentation", // Описание шаблона
  "path": "/templates/TestTemplate", // Путь к файлу main шаблона (Заполняется автоматически при создании шаблона из IDE)
  "param": [ // Список параметров, которые надо будет ввести при создании файлов из шаблона
  "name"
  ],   
  "selectParam": [  // Список параметров, которые надо будет выбрать при создании файлов из шаблона
    {
      "paramName": "ide", // Имя параметра 
      "paramValue": [ // Список вариантов выбора 
        "Android Studio",
        "Pycharm",
        "intellij"
      ]
    },
  ],
  "addFile": [ // Список файлов абстрактных шаблонов класса
    {
      "name": "{name}[-C]ViewModel.kt", // Имя, с каким создастся файл
      "path": "{ide}/viewModels/{name}[-c]", // Путь, по которому будет создан файл
      "fileTemplatePath": "example.tm" // Путь до абстрактного шаблона класса в каталоге с шаблоном
    }
  ]
}

Опции

Можно заметить, что рядом с параметрами присутствуют какие-то загадочные символы в квадратных скобках. Например, «[-C]». Всё дело в том, что параметры имеют опции, которые могут поменять значение, вставленное из параметра:  

  • [-s] - snake_case

  • [-C] - CamelCase

  • [-c] - camelCase

  • [-p] - point.between.words

  • [-sl] - slash/between/word

  • [-d] - dash-between-word

Например, написав «{"NewFeature"}[-s]», мы получим «new_feature».

Стандартные параметры

Помимо параметров, которые мы указываем в «main.json» файле, существуют стандартные параметры, которые можно применять в шаблоне.

  • {package} — пакет места, в которое вы делаете создание файла (если создать от каталога внутри модуля /ui/screen, то package будет выглядеть как ru.package.module.ui.screen).

  • {time} = Текущее время в 12-ти часовом формате - 15:56 «03:56».

  • {day} = Текущий день «04».

  • {month} = Текущий месяц «06».

  • {year} = Текущий год «2023».

Имена файлов и каталогов

Теперь давайте рассмотрим несколько особенностей и фишек плагина при работе с именами файлов и каталогов.

Если имя файла пустое, плагин создаст только цепочку каталогов, не создавая файл.

{
 "name": "",
 "path": "path/{name}[-sl]/catalog",
 "fileTemplatePath": ""
}

Для добавления файлов в Android-ресурсы пропишите «res/» в параметре «path». Плагин сам найдёт папку res в вашем модуле

{
 "name": "{name}[-s]_main.xml",
 "path": "res/layout",
 "fileTemplatePath": "layout.tm"
}

Чтобы добавить Unit-тест напишите «test/» в параметр «path». Плагин сам найдёт папку test в вашем модуле.

{
 "name": "{name}[-C]ViewModelTest.kt",
 "path": "test/viewModels/{name}[-c]",
 "fileTemplatePath": "example.tm"
}

Бывают случаи, что нам надо создать файлы не только там, где мы вызвали, но и в корне проекта.

Чтобы создать файл в корне проекта, напишите «~/» в «path». 

{
 "name": "{name}[-C].txt",
 "path": "~",
 "fileTemplatePath": "example.tm"
}

Актуальные примеры шаблона можно посмотреть тут.

Что писать в .tm файле?

В tm файле находится ваш шаблон. Размечаем его параметрами, которые создали в main.json -> (param или selectParam)

К параметрам применяем нужные нам опции и собственно всё.

package {package}.analytics

import ru.cian.presentation.analytics.base.sender.AnalyticsSender
import {package}.analytics.builder.{name}[-C]Builder
import javax.inject.Inject

class {name}[-C]Analytics
@Inject constructor(
   private val analyticsSender: AnalyticsSender
) {

   fun {name}[-c]() {
       val builder = {name}[-C]Builder()
       analyticsSender.sendEvent(builder)
   }
}

Если вы добавили .tm файл в шаблон не через IDE, а руками, то не забудьте прописать его в «main.json» как показано выше.

Как создать шаблон?

  • Вводим название вашего будущего шаблона в диалоговом окне.

  • Чтобы создать новый шаблон, нажмите на пункт меню «Tools» -> «Create New Template».

  • Готово. В вашей папке с проектом в подпапке «templates» появился новый шаблон.

Как добавить файл в свой шаблон?

  • Чтобы добавить файл в шаблон, щёлкните по этому файлу правой кнопкой мыши и выберите «Add File in Template».

  • Плагин попросит вас выбрать, в какой шаблон вы хотите добавить файл (если у вас их несколько).

  • Переименовываем файл так, как он будет назван в шаблоне.

  • Готово, файл появился в выбранном шаблоне и виден в main.json.

"addFile": [
 {
   "name": "build.gradle.kts",
   "path": "",
   "fileTemplatePath": "buildTemp.tm"
 },
]

Как использовать созданный шаблон?

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

  • Заполняем параметры в диалоговом окне и нажимаем  «OK».

  • Готово, создалась целая структура из шаблона!

Итого!

Что плагин умеет делать, мы разобрались, но чего он пока не умеет делать?

Пока что, не умеет вставлять куски кода в уже существующие файлы (фича на будущее), и чтобы подключить новые модули к app я создаю helpFile.md файл, в котором я прописываю путь до файла, и что в него надо вставить.

В main.json

{
 "name": "helpFile.md",
 "path": "~",
 "fileTemplatePath": "helpFile.tm"
}

и сам шаблон файла

[Добавить в](app/build.gradle.kts)

   implementation(project(":kmm_{name}[-s]_api"))
   runtimeOnly(project(":kmm_{name}[-s]_impl"))
   runtimeOnly(project(":{name}[-s]_compose"))

[Добавить в](settings.gradle.kts)

   includeKmmFeatureModule("{name}[-s]")

Плагин помогает мне создавать большие модули, например, для использования kmm нам надо создать 3 модуля, в которых 8 каталогов.

  • api

    • androidMain

    • commonMain

  • impl

    • androidMain

    • androidTest

    • commonMain

    • commonTest

    • iosMain

  • ui module

    • androidMain

Он значительно сокращает время от старта фичи в работу до начала написания бизнес логики. 

Использую даже для мелких задач, например, нам нужно создать из endPoint дерево каталогов. 

Из - /getData/point/v2/map-search

Преобразует в дерево каталогов

Если у вас остались вопросы, пишите мне, всем с радостью отвечу!

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

Ccылка на Github

Ссылка на Marketplace

Комментарии (2)


  1. OldFisher
    09.06.2023 10:59

    Аки Танос - это безвозвратно разнести в пыль половину проекта.


    1. Louco11 Автор
      09.06.2023 10:59

      Нуууу, тут аналогия что также быстро это сделать =))