Когда собаке программисту нечего делать, он начинает все автоматизировать. Мне по роду своей деятельности приходится писать много кода и, конечно, хочется какие-то повторяющие вещи обобщить в виде библиотек, скриптов или шаблонов для Android Studio. О них и поговорим.
Шаблоны — кто они?
Шаблон в терминах Android Studio это файл (или набор файлов) с расширением .ftl, содержащий конструкции на Java и XML (зависит от решаемой задачи), а также метаконструкции на языке шаблонизатора (template engine). Шаблонизатором в нашем случае выступает FreeMarker, язык которого является простым, но в то же время достаточно мощным для написания сложных шаблонов.
В нашем распоряжении уже есть куча разных шаблонов: activity, fragments, services, widgets, UI-компоненты, директории и многое другое. Но бывают случаи (такие как наш), когда существующих шаблонов недостаточно и нужно делать свой. И тут нас постигает первое “удивление”: информации по этому делу очень мало. Есть несколько статей в блогах и чудом извлеченная из недр GoogleGit скудная документация.
Осмотр территории
Учиться лучше всего на примерах, поэтому будем разбирать достаточно простой, но в то же время содержащий все важные аспекты шаблон пустого фрагмента. Добыть его можно из ANDROID_STUDIO_DIR/plugins/android/lib/templates/other/BlankFragment. Лучше куда-нибудь скопировать содержимое этого каталога, чтобы ничего не сломать своими экспериментами. Для начала разберемся с тем, что там есть.
- globals.xml.ftl — набор глобальных для шаблона переменных
- root/res/layout/fragment_blank.xml.ftl — шаблон разметки фрагмента
- root/res/values/strings.xml — строковые константы, которые будут добавлены в проект
- root/src/app_package/BlankFragment.java.ftl — шаблон кода
- template.xml — метаданные шаблона
- template_blank_fragment.png — самый важный файл! Пиктограмма фрагмента в данном случае
Понять, что происходит в каждом из файлов, не составляет труда. Проблемы начинаются, когда возникает необходимость кастомизировать происходящее в них или даже вообще написать что-то свое с нуля. В этом немного может помочь официальная документация
А где же реальные примеры?!
Разобраться в нюансах шаблона BlankFragment — это только полдела. Чтобы закрепить полученные знания, нужно сделать что-то свое. Кому лень придумывать свои варианты, могут взять мой. Остальные смотрят и вдохновляются.
В наших приложениях мы используем архитектуру MVP, согласно которой каждый фрагмент это View, а любой View нужен Presenter. Оставим в стороне все, что касается Model-слоя и посчитаем классы:
- FragmentView + ViewInterface
- FragmentPresenter + PresenterInterface
На первый взгляд не так много. Но если представить это на практике, все получается не так радужно. Нужно создать фрагмент и разметку к нему, прописать эту разметку во фрагменте, спроектировать View-интерфейс, реализовать этот интерфейс пока в виде пустых методов. То же самое придется проделать с Presenter-ом. А ведь это еще нужно раскидать по пакетам, вы же не хотите, чтобы ваш проект был одноуровневым адом для классов, верно? Также нужно связать View и Presenter, что тоже требует написания кода руками. Со всем этим, конечно, можно жить, но такая рутина быстро надоедает. Тут и приходят на помощь шаблоны. С их помощью мы и автоматизируем все эти рутинные действия по созданию архитектурных кусков.
Через тернии к звездам
Начнем с реализации файла template.xml. Именно его содержимое вы видите в красивых окнах при создании activity, фрагментов и т.п. Мы пойдем по самому простому пути и сделаем наш MVP-шаблон на базе существующего шаблона EmptyActivity. Копируем себе этот каталог, переименовываем в MVPActivity и приводим файл template.xml к следующему виду:
<?xml version="1.0"?> <template format="5" revision="1" name="MVP Activity" minApi="7" minBuildApi="14" description="Creates a new MVP activity"> <category value="MVP" /> <formfactor value="Mobile" /> <parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" suggest="${layoutToActivity(layoutName)}" default="MainActivity" help="The name of the activity class to create" /> <parameter id="generateLayout" name="Generate Layout File" type="boolean" default="true" help="If true, a layout file will be generated" /> <parameter id="generateView" name="Generate View" type="boolean" default="true" help="If true, a View interface will be generated" /> <parameter id="generatePresenter" name="Generate Presenter?" type="boolean" default="true" help="If true, a Presenter interface will be generated" /> <parameter id="generatePresenterImpl" name="Generate Presenter implementation?" type="boolean" default="true" help="If true, a Presenter implementation will be generated" /> <parameter id="layoutName" name="Layout Name" type="string" constraints="layout|unique|nonempty" suggest="${activityToLayout(activityClass)}" default="activity_main" visibility="generateLayout" help="The name of the layout to create for the activity" /> <parameter id="isLauncher" name="Launcher Activity" type="boolean" default="false" help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" /> <parameter id="viewName" name="View Name" type="string" constraints="class|nonempty|unique" default="MainView" visibility="generateView" suggest="${underscoreToCamelCase(classToResource(activityClass))}View" help="The name of the View interface to create" /> <parameter id="presenterName" name="Presenter Name" type="string" constraints="class|nonempty|unique" default="MainPresenter" visibility="generatePresenter" suggest="${underscoreToCamelCase(classToResource(activityClass))}Presenter" help="The name of the Presenter interface to create" /> <parameter id="presenterImplName" name="Presenter Implementation Name" type="string" constraints="class|nonempty|unique" default="MainPresenterImpl" visibility="generatePresenterImpl" suggest="${underscoreToCamelCase(classToResource(activityClass))}PresenterImpl" help="The name of the presenter implementation class to create" /> <parameter id="packageName" name="Package name" type="string" constraints="package" default="com.mycompany.myapp" /> <!-- 128x128 thumbnails relative to template.xml --> <thumbs> <!-- default thumbnail is required --> <thumb>template_blank_activity.png</thumb> </thumbs> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template>
Разобраться в нюансах вам поможет официальная документация, я же остановлюсь на основных отличиях. Во-первых, были добавлены блоки вида:
<parameter id="generateView" name="Generate View" type="boolean" default="true" help="If true, a View interface will be generated" />
Которые будут отображаться в виде чекбоксов, позволяющих включать/выключать генерацию тех или иных компонентов. Во-вторых, были добавлены поля для ввода имен классов и интерфейсов View и Presenter-ов:
<parameter id="viewName" name="View Name" type="string" constraints="class|nonempty|unique" default="MainView" visibility="generateView" suggest="${underscoreToCamelCase(classToResource(activityClass))}View" help="The name of the View interface to create" />
Обязательно обратите внимание на атрибут visibility. Именно в нем прописан id-шник чекбокса, который отвечает за отображение этого поля. Основная же магия происходит в атрибуте suggest. Здесь мы убираем все возможные символы подчеркивания, отрезаем суффикс ‘Activity’ и добавляем свой суффикс ‘View’. С остальным содержимым этого файла, думаю, вопросов возникнуть не должно.
Теперь настала очередь шаблонов архитектурных компонентов. Как мы помним, располагать это все нужно в каталоге root/src. Организуем каталог следующим образом:
- root/src
- app_package
- presentation
- implementation
- presenter
- view
- ui
- activity
- presentation
- app_package
После этого можно заняться непосредственно шаблонными файлами. Начнем с SimpleActivity.java.ftl, который достался нам по наследству от базового шаблона. Его необходимо переместить в каталог ui/activity и привести к следующему виду:
package ${packageName}.ui.activity; import ${superClassFqcn}; import android.os.Bundle; import ${packageName}.R; <#if generateView>import ${packageName}.presentation.view.${viewName};</#if> <#if generatePresenter>import ${packageName}.presentation.presenter.${presenterName};</#if> <#if generatePresenterImpl>import ${packageName}.presentation.implementation.${presenterImplName};</#if> public class ${activityClass} extends ${superClass} <#if generateView>implements ${viewName}</#if>{ <#if generatePresenter> private ${presenterName} mPresenter; </#if> @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); <#if generateLayout> setContentView(R.layout.${layoutName}); </#if> <#if generatePresenterImpl> mPresenter = new ${presenterImplName}(this); </#if> } <#if generatePresenter> @Override protected void onDestroy() { mPresenter.onDestroy(); super.onDestroy(); } </#if> }
Основное отличие от исходного файла состоит в том, что добавлена реализация интерфейса View, а так же добавлено создание и уничтожение Presenter-а. Так как ранее мы сделали эти вещи опциональными, это и было отражено в коде шаблона в виде условий <#if>…</#if>.
Теперь задачка чуть-чуть посложнее: написать шаблонный файл с нуля. Но не пугайтесь, полученных ранее знаний более чем достаточно для решения этой задачи. У вас должно получиться примерно так:
package ${packageName}.presentation.view; public interface ${viewName} { }
package ${packageName}.presentation.presenter; public interface ${presenterName} { void onDestroy(); }
package ${packageName}.presentation.implementation; import ${packageName}.presentation.view.${viewName}; import ${packageName}.presentation.presenter.${presenterName}; public class ${presenterImplName} implements ${presenterName} { private ${viewName} mView; public ${presenterImplName}(final ${viewName} view) { mView = view; } @Override public void onDestroy() { mView = null; } }
Конечно же, каждый из этих файлов должен находиться в своем пакете. Теперь, когда ингредиенты готовы, нам нужен рецепт для приготовления всего этого добра.
recipe.xml.ftl
В отличие от оригинального файла здесь добавляется опциональное инстанцирование наших архитектурных компонент. Файл globals.xml.ftl мы оставим без изменений, а картинку вы можете нарисовать сами или взять мою.
The End
На этом создание шаблона можно считать оконченным. Пришла пора посмотреть на результат. Для этого топаем в ANDROID_STUDIO_DIR/plugins/android/lib/templates/activities и копируем в него каталог с нашим шаблоном MVPActivity. Запускаем студию, идем в File -> New, ищем новую категорию MVP, открываем из нее наш шаблон и радуемся получившемуся результату.
И для тех, кто по какой-то причине пропустил ссылки в тексте статьи, я привожу их здесь общим списком:
ссылка на оригинал статьи http://habrahabr.ru/post/274897/
Добавить комментарий