Android StudioのFile Templates解説

今仕事で開発している案件はMVVMをベースに作っているのですが、そのときに必ず用意するファイル群があるため、Templateを使ってみました。そのときの知見を備忘録として残しておきます。

サンプルも用意しましたので、参考にどうぞ。
https://github.com/shcahill/AndroidFileTemplateSample

## Templateのpath
デフォルトではここにTemplateが置かれています。もしかしたらAndroid Studioのバージョンによって変わるかもしれません。筆者はAndroid Studio 3.1.xで確認しています。

```sh
/Applications/Android Studio.app/Contents/plugins/android/lib/templates/
```

今回はFragmentとその他のファイルを追加したかったので、`/other`ディレクトリにある`BlankFragment`を参考に作りました。

# 必要なファイル
- テンプレートファイル
    - java/kotlin/resource xmlの元になるファイル
- globals.xml.ftl
    - 環境変数の定義
    - 拡張子ftlはFreeMarker Template Languageのことのようです
- recipe.xml.tfl
    - テンプレートファイルから実際のソースコードを生成するプログラム
- template.xml
    - 下のダイアログの内容を定義するファイル
    - ダイアログの左側の画像は、なくてもOK

![スクリーンショット 2018-06-05 13.38.11.png](https://qiita-image-store.s3.amazonaws.com/0/202533/446faccf-e7d1-5255-13da-82c53287acca.png)

次で各ファイルの役割について述べていきます。

## globals.xml.ftl
サンプルとしてBlankFragmentのxml.tflにコメントを記載する形で紹介します。

```xml:globals.xml.ftl
<?xml version="1.0"?>
<globals>
    //rootはtemplateフォルダ直下です。
    <#include "root://activities/common/common_globals.xml.ftl" />
    <#assign useSupport=appCompat>
    <global id="useSupport" type="boolean" value="${useSupport?string}" />
    <global id="SupportPackage" value="${useSupport?string('.support.v4','')}" />
    <global id="resOut" value="${resDir}" />
    <global id="srcOut" 
        //関数については次で紹介します
        value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
```

Kotlinの場合はKotlin用のsrcDirに変えてあげる必要があります。
こちらが参考になります。
[KotlinのProject Templete作る時の注意点](https://qiita.com/k_keisuke/items/38966eb71af7fb5158ed)

### 関数

上記の`globals.xml.ftl`にも含まれていますが、ftl内で扱うことのできる関数がいくつかあります。すべての関数は引数に文字列を受け取り、文字列を返却する関数です。

- `activityToLayout`
    - 例：HogeActivity > activity_hoge
- `camelCaseToUnderscore`
    - 例：FooBar > foo_bar
- `escapeXmlAttribute`
    - xml属性値として認識できるようescapeします
    - `'`、`"`、`<`、`&`などが対象です
- `escapeXmlText`
    - XMLテキストとして使用できるようにescapeします
    - `<`と`>`はescapeされますが、`escapeXmlAttribute`と異なり、`'`と`"`はescapeしません
    - 例：`A & B's` > `A &amp; B\s`
- `escapeXmlString`
    - ResStringに用いる場合のescapeに適しています
- `extractLetters`
    - 句読点や空白を除去します
- `classToResource`
    - 文字列から`Activity`,`Fragment`,`Provider`,`Service`といった　suffixを取り除き、リソース名に適した文字列に変換してくれます
- `layoutToActivity`
    - 例：`activity_hoge` > `HogeActivity`
- `slashedPackageName`
    - 例：`com.hoge` > `com/hoge`
- `underscoreToCamelCase`
    - 例：`foo_bar` > `fooBar`

## recipe.xml.tfl

```xml:recipe.xml.tfl
<?xml version="1.0"?>
//kotlin_macros.ftlをktという名前で読み込みます
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
    //上で読み込んだkotlin_macros.ftl(=kt)のaddAllKotlinDependenciesを実行する
    //具体的には、Kotlinの場合は、必要なdependenciesをgradleに追加する処理が実行されます
    <@kt.addAllKotlinDependencies />
    //useSupportはglobals.xml.ftlで定義されたパラメータです。必要であればsupportLibをgradleに追加しています
    <#if useSupport><dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/></#if>
    //templateファイルで用意されたstrings.xmlを既存のstrings.xmlにマージします
    <merge from="root/res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

    <#if includeLayout>
        <instantiate from="root/res/layout/fragment_blank.xml.ftl"
                       to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />

        <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
    </#if>

    //fromタグのファイルを元に、実ファイルをtoタグのpath位置に生成します
    <instantiate from="root/src/app_package/BlankFragment.${ktOrJavaExt}.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${className}.${ktOrJavaExt}" />

    //ここで指定されたファイルがtemplate作成完了後に開かれます
    <open file="${escapeXmlAttribute(srcOut)}/${className}.${ktOrJavaExt}" />
</recipe>
```


## template.xml

```xml:template.xml
<?xml version="1.0"?>
//このtemplateの概要を定義します
<template
    format="5" //Format Versionのことです。2018/06時点では5が最新のようです
    revision="3" //管理番号っぽいので、自作の場合は1スタートでいいと思います。
    name="Fragment (Blank)" //画像(1)の右側で表示されている名前です
    description="Creates a blank fragment that is compatible back to API level 4." //説明文です。ダイアログ上部に表示されます
    //optional.このtemplateが必要な最低APIレベルです
    minApi="7"
    //optional.このtemplateが必要な最小ビルドターゲットAPIレベルです
    minBuildApi="8">

    //画像(1)の左側に表示されているメニューカテゴリの名前です
    //任意の文字列を指定可能です
    <category value="Fragment" />

    //このtemplateが依存するライブラリを示します。gradleに記述がない場合は追記されます
    <dependency 
       //supportLibのv4とv13が対象のようです
       name="android-support-v4"
       //このtemplateが必要とするlibraryの最小バージョンです
       revision="8" />

    //ユーザに入力させるパラメータ群です。後述します
    <parameter
        id="className"
        name="Fragment Name"
        type="string"
        constraints="class|nonempty|unique"
        default="BlankFragment"
        help="The name of the fragment class to create" />

    <parameter
        id="includeLayout"
        name="Create layout XML?"
        type="boolean"
        default="true"
        help="Generate a layout XML for the fragment" />

    <parameter
        id="fragmentName"
        name="Fragment Layout Name"
        type="string"
        constraints="layout|nonempty|unique"
        default="fragment_blank"
        visibility="includeLayout"
        suggest="fragment_${classToResource(className)}"
        help="The name of the layout to create" />

    <parameter
        id="includeFactory"
        name="Include fragment factory methods?"
        type="boolean"
        default="true"
        help="Generate static fragment factory methods for easy instantiation" />

    <parameter
        id="includeCallbacks"
        name="Include interface callbacks?"
        type="boolean"
        default="true"
        help="Generate event callbacks for communication with an Activity or other fragments" />

    //画像を貼るときはこちらへ
    <thumbs>
        <thumb>template_blank_fragment.png</thumb>
    </thumbs>

    //ここは環境変数や実行ファイルの定義なのでそのまま書きます。
    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />

</template>
```
画像(1)
![スクリーンショット 2018-06-05 13.47.33.png](https://qiita-image-store.s3.amazonaws.com/0/202533/2910d73e-4c21-46e4-4760-47f33ccea803.png)
左側がcategoryで指定したグループ、右側がnameで指定した文字列が採用されます。
### parameters
- `id`
    - 一意のIDです
    - 本ファイル内の他パラメータや、`recipe.xml.ftl` でファイルを生成する場合などに参照します
    - 参照する場合は`${hoge}` と書きます
- `name`
    - パラメータ名です。ダイアログに表示されます
- `constraints`(optional)
    - 以下のパラメータが指定できます（`|`で複数指定可能）
        - `nonempty` : 空欄を許可しない
        - `unique` : 一意であることを保証
        - `exists`: 既存の値であることを保証(すでにあるLayoutファイルなどを指定する場合に使用)
        - `apilevel`: API levelのInt型
        - `package` : パッケージとして認識される
        - `class` : クラス名であることを保証 
        - `activity`: Activity名であることを保証
        - `layout`: Layoutリソース名であることを保証
        - `drawable`: Drawableリソース名であることを保証
        - `string`: Stringリソース名であることを保証
        - `id`: リソースIDであることを保証

    - classやlayoutなどを指定すると、日本語や先頭に数字など適さないフォーマットは入力できなくなります
- `help`
    - 入力のフォーカスが当たっているとダイアログの下部に説明文として表示されます
- `default`(optional)
    - デフォルト値を設定できます
- `suggest`(optional)
    - 他のパラメータと連動して名前を動的に変更させたい場合に使います
    - `default`でも同じ動きをしていたので、明確な使い分け方はよく分かりませんでした…
- `visibility`
    - 下記のような使い方ができます
    - `visibility="value1 == 'value2'"`
- `type`
    - 以下のパラメータが指定できます
        - `string`
        - `boolean`:チェックボックスの表示になります
        - `enum`:プルダウンで選択。下記のようにリストを複数指定します
            - `<option id="item1">Hoge</option>`
            - サンプルを下に記載しました

```xml:enum-sample
<parameter
   id="minWidth"
   name="Minimum Width (cells)"
   type="enum"
   default="1">
   <option id="1">1</option>
   <option id="2">2</option>
   <option id="3">3</option>
   <option id="4">4</option>
</parameter>
```

# 使用方法

デフォルトでは前述のとおり、template群は下記pathに保存されています。同様に、新しく作ったtemplateもフォルダを作って配置してください。
`/Applications/Android Studio.app/Contents/plugins/android/lib/templates/`
場所はtemplates以下ならどこでも大丈夫そうなので、自作Templateはtemplatesフォルダの下に任意のフォルダを作成してまとめておくとわかりやすいと思います。
その後、Android Studioを再起動すると自作したtemplateが選択できるようになります。

## おまけ
作ったテンプレートの管理方法ですが、私はプロジェクトの下にdocファイルを用意して、そこに配置することでgit管理しています。

```
/doc/templates/HogeTemplate/
```
また、デフォルトの場所には簡単に配置できるようにscriptも置いておきました。
（pathが異なる場合なんかの考慮はできていません…）

```sh
cp -r -i doc/templates/* /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/other/
```