Тотальна шаблонизация



Коли собаці програмісту нема чого робити, він починає все автоматизувати. Мені за родом своєї діяльності доводиться писати багато коду і, звичайно, хочеться якісь повторюють речі узагальнити у вигляді бібліотек, скриптів або шаблонів для Android Studio. Про них і поговоримо.



Шаблони — хто вони?

Шаблон в термінах Android Studio це файл (або набір файлів) з розширенням .ftl, що містить конструкції на Java і XML (залежить від розв'язуваної задачі), а також метаконструкции мовою шаблонизатора (template engine). Шаблонизатором в нашому випадку виступає FreeMarker, мова якого є простим, але в той же час досить потужним для написання складних шаблонів.

В нашому розпорядженні вже є купа різних шаблонів: activity, fragments, services, віджети, 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 до наступного вигляду:

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
Після цього можна зайнятися безпосередньо шаблонними файлами. Почнемо з SimpleActivity.java.ftl, який дістався нам у спадок від базового шаблону. Його необхідно перемістити в каталог ui/activity і привести до наступного вигляду:

SimpleActivity.java.ftl
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>.

Тепер задачка трохи складніше: написати шаблонний файл з нуля. Але не лякайтеся, отриманих раніше знань більш ніж достатньо для вирішення цієї задачі. У вас повинно вийти приблизно так:

SimpleView.java.ftl
package ${packageName}.presentation.view;

public interface ${viewName} {

}


SimplePresenter.java.ftl
package ${packageName}.presentation.presenter;

public interface ${presenterName} {

void onDestroy();
}


SimplePresenterImpl.java.ftl
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
recipe.xml.ftl


На відміну від оригінального файлу тут додається опціональне инстанцирование наших архітектурних компонент. Файл globals.xml.ftl ми залишимо без змін, а картинку ви можете намалювати самі або взяти мою.

The End

На цьому створення шаблону можна вважати закінченим. Прийшла пора подивитися на результат. Для цього тупотимо в ANDROID_STUDIO_DIR/plugins/android/lib/templates/activities і копіюємо в нього каталог з нашим шаблоном MVPActivity. Запускаємо студію, йдемо в File — > New, шукаємо нову категорію MVP, відкриваємо з неї наш шаблон і радіємо отриманого результату.

І для тих, хто з якоїсь причини пропустив посилання в тексті статті я привожу їх тут загальним списком:


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.