Your web browser is out of date. Update your browser for more security, speed and the best experience on this site.

Update your browser
CapTech Home Page

Blog March 1, 2019

Creating Custom Templates in Android Studio

CapTech

Android Studio comes prepackaged with a handy templating tool that creates new activities, fragments, content providers, layouts, and more.

While useful, these templates are often too generic for real-world applications, as they don't extend the required base classes common among MVP and MVVM architectures. With a little tweaking, Android Studio's templating engine can accommodate your project-specific needs, automatically wiring together any number of custom fragments, activities, view models, presenters, adapters, and XML resources.

Custom MVP Fragment Created By Template

Code templating lets teams record their official design patterns in a central location, provides a useful teaching tool for newly on-boarded personnel, and spares developers from keying in clunky boilerplate code.

Environmental Setup

Setup is required before we can develop new templates. Ultimately, Android Studio sources templates from its local templates directory.

Local Templates Folder

/Applications/Android

Studio.app/Contents/plugins/android/lib/templates

While you can add your own templates here directly, doing so would make those templates available to you alone, with no backup. Those working collaboratively need a way to share their templates team-wide. To address this concern, a shell script can keep your repo and local templates synced.

First, define a folder for your templates1. We’ll put ours in app/src/templating. Add an "install" shell script and a new template folder, shown below.

Repo Templates Folder

Our install script will copy all underscore-prefixed2 folders (e.g. _TemplateDemo) into our local templates folder.

#!/bin/bash
find /Applications -path "*.app" -prune \( -name "*Android Studio*" \) -print0 | while read -d 
<p> </p>
<p>
 An app-level build.gradle task makes invoking your shell script easier than
 running from command line. If you want, you can hook it to the Clean<sup>3</sup>
 task, so templates can be synced within the Android Studio UI.
</p>
<pre><code>android {
 ...
 afterEvaluate {
 task syncTemplates {
 doLast {
 println "Syncing templates to your file system."
 "app/src/templating/install.sh".execute()
 }
 }
 clean.dependsOn(syncTemplates)
 }
}</code></pre>
<p> </p>
<p>
 Now the local plugin folder gets synced whenever we invoke the script.
 <span> </span>
</p>
<h1>
 <span><img alt="Local Templates Folder with Custom" src="https://img.captechconsulting.com/library/bb3fc0cc7acb4472957a11307206e1cb.ashx?h=209&w=284" style="width:284px;height:209px;"></span>
</h1>
<h1>
 </h1><div class="rrHandle rrSW">
 
 </div>
 <div class="rrHandle rrS">
 
 </div>

<h1>
 <span>
 </span></h1><div class="rrHandle rrE">
 
 </div>
 <div class="rrHandle rrNE">
 
 </div>
 <div class="rrHandle rrN">
 
 </div>
 <div class="rrHandle rrNW">
 
 </div>
 <div class="rrHandle rrW">
 
 </div>
 

<p>
 <span><sup>/Applications/Android
 Studio.app/Contents/plugins/android/lib/templates/other</sup></span>
</p>
<h2><span>Wiring Up Templates</span></h2>
<p>
 Templates use
 <span><a href="https://freemarker.apache.org/">Apache FreeMarker</a></span><span> </span>to manage file generation, which depends on the following:
</p>
<ul style="list-style-type:disc;"><li>
 <strong>Template.xml</strong>: The template’s configuration, including
 its name, description, parameters, etc.
 </li>
 <li>
 <strong>globals.xml.ftl</strong>: Simple file used to define global
 variables.
 </li>
 <li>
 <strong>recipe.xml.ftl</strong>: Responsible for all file actions –
 e.g. instantiating, opening in the editor, adding to the manifest, etc.
 </li>
 <li>
 <strong>Source files</strong>: Your base files that FreeMarker evaluates to
 create new project files. 
 </li>
</ul><h2>Folder Structure</h2>
<p>
 Here’s a conventional template hierarchy, straight from Android’s
 own EmptyActivity template. Custom templates should follow this pattern.
 
</p>
<h2>
 <img alt="Example Template Package" src="https://img.captechconsulting.com/library/038095e895574dcebf74f04ccc3fe94c.ashx?h=294&w=319" style="width:319px;height:294px;"></h2>
<h2>Template.xml</h2>
<p>
 Here's where we define the template's characteristics and guide its behavior
 in the prebuilt <em>Create From Template</em> UI. (The UI itself is not
 accessible to us.) 
</p>
<p>
 An important note: modifying Template.xml requires an Android Studio restart
 before it takes effect. This sounds annoying (rightly so), but there's good
 news: the other files get called at runtime, so restarting isn’t needed
 terribly often.
</p>
<pre><code><?xml version="1.0"?>
<template
 revision="1"
 name="CapTech MVP Fragment"
 description="Stubs out a fragment, presenter, view, and layout resource file."
 minApi="14"
 minBuildApi="8">

 <category value="CapTech" />

 <dependency name="android-support-v4" revision="8" />

 <parameter
 id="className"
 name="Feature name"
 type="string"
 constraints="class|nonempty|unique"
 default="ExampleFeature"
 help="Feature name (omit 'fragment' suffix)" />

 <thumbs>
 <thumb>templates_captech_demo.png</thumb>
 </thumbs>

 <globals file="globals.xml.ftl" />
 <execute file="recipe.xml.ftl" />

</template></code></pre>
<p>
 All parameters show up in the <em>Create</em> UI<sup>4</sup> and are available
 to your source files by their ID.
</p>
<p>
 <strong>Parameter</strong> types can be string, boolean, or enum, the latter
 requiring additional <em>option </em>tags which render as a dropdown box in
 the <em>Create </em>UI.
</p>
<p>
 <strong>Constraints</strong> are pipe-separated to allow for multiple
 specification, and include the following limiters: noempty, apilevel, package,
 class, activity, layout, drawable, string, id, unique, layout, exists. 
</p>
<h2>Globals.xml</h2>
<p>
 Not much to see here, other than a file of globally defined variables. Utility
 methods (like slashedPackageName) are described below.
</p>
<pre><code><?xml version="1.0"?>
<globals>
 <global id="resOut" value="${resDir}" />
 <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals></code></pre>
<h2>Recipe.xml</h2>
<p>
 Think of this as your controller, where actions are defined. Here we can
 instantiate template files, open new files in our editor, verify dependencies,
 and even merge the Android Manifest to include new activities.
</p>
<pre><code><!--?xml version="1.0"?-->
<recipe>
 <instantiate from="root/res/layout/fragment_demo.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml"></instantiate>
 <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml"></open>

 <instantiate from="root/src/app_package/DemoView.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}View.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}View.java"></open>

 <instantiate from="root/src/app_package/DemoPresenter.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}Presenter.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}Presenter.java"></open>

 <instantiate from="root/src/app_package/DemoFragment.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}Fragment.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}Fragment.java"></open>
</recipe></code></pre>
<p>
 For our demo, we simply instantiate a layout file, view, presenter, and
 fragment, opening each in the IDE, respectively. Other notable recipe methods
 include 1) copy, which copies a file without processing it, and 2) merge,
 which merges a source file into an existing one, such as strings.xml or
 AndroidManifest.xml.
</p>
<p>Note that you cannot copy <em>from</em> your repo's files<sup>5</sup>.</p>
<p>
 Depending on your implementation, the <em>from </em>attribute’s
 <em>root</em> prefix may not be required.
</p>
<h2>Source Files (root)</h2>
<p>
 These files get consumed by FreeMarker and injected into your project. The
 implementation is relatively straight-forward: substitute your dynamically
 generated content, based on parameters and global variables, into your classes
 as needed.
</p>
<pre><code>package ${packageName};

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.captech.templatedemo.base.MvpFragment;

public class ${className}Fragment extends MvpFragment<${className}View, ${className}Presenter> implements ${className}View {

 private static String EXTRA_EXAMPLE_ARG = "extra_example_arg";

 TextView exampleTextView;

 public static ${className}Fragment newInstance(@Nullable String exampleArg) {
 Bundle bundle = new Bundle();
 if (exampleArg != null) {
 bundle.putString(EXTRA_EXAMPLE_ARG, exampleArg);
 }
 ${className}Fragment fragment = new ${className}Fragment();
 fragment.setArguments(bundle);
 return fragment;
 }

 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 return inflater.inflate(R.layout.fragment_example, container, false);
 }

 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 exampleTextView = view.findViewById(R.id.fragment_example_textView);
 }

 @Override
 public ${className}Presenter createPresenter() {
 return new ${className}Presenter();
 }

 @Override
 public void loadExampleText(String exampleText) {
 exampleTextView.setText(exampleText);
 }

 <#if yourBooleanParameter>
 // Other code to show if/else logic
 </#if >

}</code></pre>
<p>
 If you don’t specify a package name in the UI, it will be inferred based
 on your selected directory when you invoked the <em>Create</em> UI. The
 if/then functionality is especially powerful when coupled with boolean and
 enum parameter types, allowing you to create several different flavors of your
 implementation based on a single template.
</p>
<h2><span>Helpful Template Methods</span></h2>
<p>
 Several utility methods can assist you in generating properly formatted names
 and references.
</p>
<ul style="list-style-type:disc;"><li>
 slashedPackageName(string): e.g. com.myproject.test to com/myproject/test
 </li>
 <li>layoutToActivity(string): e.g. activity_test to TestActivity</li>
 <li>activityToLayout(string): e.g. TestActivity to activity_test</li>
 <li>
 classToResource(string): Strips the phrases Activity, Fragment, Provider,
 Service; e.g. ResourceActivity becomes Resource.
 </li>
 <li>camelCaseToUnderscore(string): self-explanatory</li>
 <li>underscoreToCamelCase: self-explanatory</li>
 <li>escameXmlAttribute(string) : self-explanatory</li>
 <li>escapeXmlText(string) : self-explanatory</li>
 <li>escapeXmlString(string) : self-explanatory</li>
 <li>extractLetters(string): removes any punctuation and/or whitespace</li>
</ul><h2><span>Development Tips</span></h2>
<ul style="list-style-type:disc;"><li>
 <strong>The Process</strong>. Development of shareable templates goes like
 this: tweak your implementation (source, recipe, etc.), run your sync task,
 restart if necessary, then try creating your template again. Your latest
 templated files should reflect the new changes.
 </li>
 <li>
 <strong>Use Undo</strong>. You can undo all newly created files after
 completing the template user flow<sup>6</sup>. This is particularly useful
 during development while fine-tuning your implementation. Without this, you
 must locate and delete your new files individually.
 </li>
 <li>
 <strong>Enable Auto-Import</strong>. Enabling auto-import allows you to pass
 in unqualified references as input parameters to your templates. That way,
 you can pass in MyWhateverController to your new class files and let Android
 Studio resolve the import for you.
 </li>
 <li>
 <strong>Mind the Bubble</strong>. FreeMarker errors manifest themselves as
 an error window in the lower right corner of Android Studio. This mini
 stackrace can help you identify template-induced errors<sup>7</sup>.
 </li>
 <li>
 <strong>Leverage What's There</strong>. Android Studio's pre-installed
 templates are fully available to you, providing a diverse array of
 implementations. They’re a great resource for examples and
 inspiration.
 </li>
</ul><h2><span>Disclaimer</span></h2>
<p>Some general words of caution:</p>
<ul style="list-style-type:disc;"><li>
 <strong>With Great Power…</strong> While templating can be
 extraordinarily useful in manufacturing complex code, it's not a replacement
 for sound architecture. Reusable base classes are strongly preferred where
 possible. The less code to maintain, the better.
 </li>
 <li>
 <strong>No Official Support</strong>. While templates have been a part of
 Android Studio for years, customized templates aren't officially supported.
 Though unlikely, future Android Studio versions could (theoretically) drop
 or modify their templating strategy, leaving your templates in limbo. Since
 implementing new templates is relatively easy, losing them is perhaps a
 tolerable risk, but a risk nonetheless – particularly if implementing
 highly complex configurations.
 </li>
 <li>
 <strong>Android Studio Upgrades May Require A Fresh Install</strong>. When
 upgrading, Android Studio can fail if it detects a change to its underlying
 configuration. As a result, some users may have to download the DMG/EXE from
 Android Studio's website, rather than upgrading through its UI directly.
 Luckily, these users are still able to import project settings from their
 outgoing Android Studio application, so the impact is minimal.
 </li>
 <li>
 <strong>Templates are application-wide</strong>. Be careful if dealing with
 sensitive data, as any custom templates you create will be available to all
 Android Studio projects on your machine. Custom templates are global –
 not project-specific.
 </li>
</ul><h2><span>Conclusion</span></h2>
<p>
 Templating isn’t perfect<sup>8</sup>, but its relatively simple
 implementation can produce significant value from minimal investment.
 Templating can be a great addition to any Android developer's arsenal –
 particularly those working with modern, file-heavy architectures (e.g.
 MVP/MVVM) that emphasize the separation of concerns. With templating,
 developers can easily stand up their interdependent files without enduring the
 headache of wiring them together manually, resulting in the rapid, automated
 creation of enterprise-ready source code. <span> </span>
</p>
<p>Notes:</p>
<ol><li>
 Your repo’s templates folder location is arbitrary. You can place it
 elsewhere, but doing so will invalidate several paths referenced throughout
 this article.
 </li>
 <li>
 You don’t need an underscore prefix by rule, but you need some way of
 identifying which subdirectories should be migrated into Android
 Studio’s local plugins folder. You could create a subdirectory for
 them, or use some other naming convention, etc., but doing so would require
 modification of the install script provided.<span>
 </span>
 </li>
 <li>
 The Clean is not required for the templates to sync. Hooking to the Clean
 task is for convenience only – it gives your teammates an easy way to
 sync templates within the Android Studio UI. It’s easier to ask your
 colleagues to “clean the project” than to “invoke the
 syncTemplates gradle task from command line.”
 </li>
 <li>
 The <strong>constraints</strong> or <strong>suggest</strong> fields (which
 you may encounter while exploring other examples) do not correspond with the
 UI’s autocomplete behavior. From combing through the Android Studio
 <span><a href="https://github.com/JetBrains/android">source</a></span>, auto-complete is dictated on an exception basis by the control ID. If the
 id equals <em>parentActivityClass</em>, the UI autocompletes with the
 activity selection dialog. If the id equals <em>packageName</em>, the UI
 completes with the package selection dialog. Sadly, there is no way to
 autocomplete for class name.
 </li>
 <li>
 Copying from your repository files may appeal to you, since it would absolve
 the need for local syncing. Unfortunately, Android Studio’s
 implementation does not support this approach. It automatically defines a
 root folder and appends the <em>from</em> address to it, and consequently
 forbids copying/instantiating from your project directory. I made several
 attempts to get around this – and thus eliminate local syncing –
 with no luck.
 </li>
 <li>
 <span>The Create from Template UI will throw an error if you attempt to create
 files that already exist in your project. This is a useful protection, but
 annoying during development.
 </span>
 </li>
 <li>
 <span>The bubble is a lifesaver, but be warned: it only shows up once per
 session. You’ll have to restart before the bubble shows up again.
 </span>
 </li>
 <li>
 Here's a consolidated list of templating’s biggest flaws: no easy team
 collaboration, gathering of template data on startup, no auto-predict for
 selecting project classes, and the lack of project-specific awareness.
 Hypothetically, a custom IntelliJ plugin could address some of these
 concerns, but any developer intent on tackling these problems may consider
 contributing to Android Studio directly instead. A direct contribution
 ensures work would be put into an official solution, sanctioned by Google,
 available to everyone – and allowing you to become an Android Studio
 contributor.
 </li>
</ol>\0' folder
do
 echo "\nInstalling to $folder"
 cp -frv $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/_* "$folder/Contents/plugins/android/lib/templates/other"
done
echo "Done. Please restart Android Studio if you don't see the new template(s)."

An app-level build.gradle task makes invoking your shell script easier than running from command line. If you want, you can hook it to the Clean3 task, so templates can be synced within the Android Studio UI.

android {
 ...
 afterEvaluate {
 task syncTemplates {
 doLast {
 println "Syncing templates to your file system."
 "app/src/templating/install.sh".execute()
 }
 }
 clean.dependsOn(syncTemplates)
 }
}

Now the local plugin folder gets synced whenever we invoke the script.

Local Templates Folder with Custom

/Applications/Android Studio.app/Contents/plugins/android/lib/templates/other

Wiring Up Templates

Templates use Apache FreeMarker to manage file generation, which depends on the following:

  • Template.xml: The template’s configuration, including its name, description, parameters, etc.
  • globals.xml.ftl: Simple file used to define global variables.
  • recipe.xml.ftl: Responsible for all file actions – e.g. instantiating, opening in the editor, adding to the manifest, etc.
  • Source files: Your base files that FreeMarker evaluates to create new project files.

Folder Structure

Here’s a conventional template hierarchy, straight from Android’s own EmptyActivity template. Custom templates should follow this pattern.

Example Template Package

Template.xml

Here's where we define the template's characteristics and guide its behavior in the prebuilt Create From Template UI. (The UI itself is not accessible to us.)

An important note: modifying Template.xml requires an Android Studio restart before it takes effect. This sounds annoying (rightly so), but there's good news: the other files get called at runtime, so restarting isn’t needed terribly often.

<?xml version="1.0"?>
<template
 revision="1"
 name="CapTech MVP Fragment"
 description="Stubs out a fragment, presenter, view, and layout resource file."
 minApi="14"
 minBuildApi="8">

 <category value="CapTech" />

 <dependency name="android-support-v4" revision="8" />

 <parameter
 id="className"
 name="Feature name"
 type="string"
 constraints="class|nonempty|unique"
 default="ExampleFeature"
 help="Feature name (omit 'fragment' suffix)" />

 <thumbs>
 <thumb>templates_captech_demo.png</thumb>
 </thumbs>

 <globals file="globals.xml.ftl" />
 <execute file="recipe.xml.ftl" />

</template>

All parameters show up in the Create UI4 and are available to your source files by their ID.

Parameter types can be string, boolean, or enum, the latter requiring additional option tags which render as a dropdown box in the Create UI.

Constraints are pipe-separated to allow for multiple specification, and include the following limiters: noempty, apilevel, package, class, activity, layout, drawable, string, id, unique, layout, exists.

Globals.xml

Not much to see here, other than a file of globally defined variables. Utility methods (like slashedPackageName) are described below.

<?xml version="1.0"?>
<globals>
 <global id="resOut" value="${resDir}" />
 <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>

Recipe.xml

Think of this as your controller, where actions are defined. Here we can instantiate template files, open new files in our editor, verify dependencies, and even merge the Android Manifest to include new activities.

<!--?xml version="1.0"?-->
<recipe>
 <instantiate from="root/res/layout/fragment_demo.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml"></instantiate>
 <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml"></open>

 <instantiate from="root/src/app_package/DemoView.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}View.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}View.java"></open>

 <instantiate from="root/src/app_package/DemoPresenter.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}Presenter.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}Presenter.java"></open>

 <instantiate from="root/src/app_package/DemoFragment.java.ftl" to="${escapeXmlAttribute(srcOut)}/${className}Fragment.java"></instantiate>
 <open file="${escapeXmlAttribute(srcOut)}/${className}Fragment.java"></open>
</recipe>

For our demo, we simply instantiate a layout file, view, presenter, and fragment, opening each in the IDE, respectively. Other notable recipe methods include 1) copy, which copies a file without processing it, and 2) merge, which merges a source file into an existing one, such as strings.xml or AndroidManifest.xml.

Note that you cannot copy from your repo's files5.

Depending on your implementation, the from attribute’s root prefix may not be required.

Source Files (root)

These files get consumed by FreeMarker and injected into your project. The implementation is relatively straight-forward: substitute your dynamically generated content, based on parameters and global variables, into your classes as needed.

package ${packageName};

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.captech.templatedemo.base.MvpFragment;

public class ${className}Fragment extends MvpFragment<${className}View, ${className}Presenter> implements ${className}View {

 private static String EXTRA_EXAMPLE_ARG = "extra_example_arg";

 TextView exampleTextView;

 public static ${className}Fragment newInstance(@Nullable String exampleArg) {
 Bundle bundle = new Bundle();
 if (exampleArg != null) {
 bundle.putString(EXTRA_EXAMPLE_ARG, exampleArg);
 }
 ${className}Fragment fragment = new ${className}Fragment();
 fragment.setArguments(bundle);
 return fragment;
 }

 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 return inflater.inflate(R.layout.fragment_example, container, false);
 }

 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 exampleTextView = view.findViewById(R.id.fragment_example_textView);
 }

 @Override
 public ${className}Presenter createPresenter() {
 return new ${className}Presenter();
 }

 @Override
 public void loadExampleText(String exampleText) {
 exampleTextView.setText(exampleText);
 }

 <#if yourBooleanParameter>
 // Other code to show if/else logic
 </#if >

}

If you don’t specify a package name in the UI, it will be inferred based on your selected directory when you invoked the Create UI. The if/then functionality is especially powerful when coupled with boolean and enum parameter types, allowing you to create several different flavors of your implementation based on a single template.

Helpful Template Methods

Several utility methods can assist you in generating properly formatted names and references.

  • slashedPackageName(string): e.g. com.myproject.test to com/myproject/test
  • layoutToActivity(string): e.g. activity_test to TestActivity
  • activityToLayout(string): e.g. TestActivity to activity_test
  • classToResource(string): Strips the phrases Activity, Fragment, Provider, Service; e.g. ResourceActivity becomes Resource.
  • camelCaseToUnderscore(string): self-explanatory
  • underscoreToCamelCase: self-explanatory
  • escameXmlAttribute(string) : self-explanatory
  • escapeXmlText(string) : self-explanatory
  • escapeXmlString(string) : self-explanatory
  • extractLetters(string): removes any punctuation and/or whitespace

Development Tips

  • The Process. Development of shareable templates goes like this: tweak your implementation (source, recipe, etc.), run your sync task, restart if necessary, then try creating your template again. Your latest templated files should reflect the new changes.
  • Use Undo. You can undo all newly created files after completing the template user flow6. This is particularly useful during development while fine-tuning your implementation. Without this, you must locate and delete your new files individually.
  • Enable Auto-Import. Enabling auto-import allows you to pass in unqualified references as input parameters to your templates. That way, you can pass in MyWhateverController to your new class files and let Android Studio resolve the import for you.
  • Mind the Bubble. FreeMarker errors manifest themselves as an error window in the lower right corner of Android Studio. This mini stackrace can help you identify template-induced errors7.
  • Leverage What's There. Android Studio's pre-installed templates are fully available to you, providing a diverse array of implementations. They’re a great resource for examples and inspiration.

Disclaimer

Some general words of caution:

  • With Great Power… While templating can be extraordinarily useful in manufacturing complex code, it's not a replacement for sound architecture. Reusable base classes are strongly preferred where possible. The less code to maintain, the better.
  • No Official Support. While templates have been a part of Android Studio for years, customized templates aren't officially supported. Though unlikely, future Android Studio versions could (theoretically) drop or modify their templating strategy, leaving your templates in limbo. Since implementing new templates is relatively easy, losing them is perhaps a tolerable risk, but a risk nonetheless – particularly if implementing highly complex configurations.
  • Android Studio Upgrades May Require A Fresh Install. When upgrading, Android Studio can fail if it detects a change to its underlying configuration. As a result, some users may have to download the DMG/EXE from Android Studio's website, rather than upgrading through its UI directly. Luckily, these users are still able to import project settings from their outgoing Android Studio application, so the impact is minimal.
  • Templates are application-wide. Be careful if dealing with sensitive data, as any custom templates you create will be available to all Android Studio projects on your machine. Custom templates are global – not project-specific.

Conclusion

Templating isn’t perfect8, but its relatively simple implementation can produce significant value from minimal investment. Templating can be a great addition to any Android developer's arsenal – particularly those working with modern, file-heavy architectures (e.g. MVP/MVVM) that emphasize the separation of concerns. With templating, developers can easily stand up their interdependent files without enduring the headache of wiring them together manually, resulting in the rapid, automated creation of enterprise-ready source code.

Notes:

  1. Your repo’s templates folder location is arbitrary. You can place it elsewhere, but doing so will invalidate several paths referenced throughout this article.
  2. You don’t need an underscore prefix by rule, but you need some way of identifying which subdirectories should be migrated into Android Studio’s local plugins folder. You could create a subdirectory for them, or use some other naming convention, etc., but doing so would require modification of the install script provided.
  3. The Clean is not required for the templates to sync. Hooking to the Clean task is for convenience only – it gives your teammates an easy way to sync templates within the Android Studio UI. It’s easier to ask your colleagues to “clean the project” than to “invoke the syncTemplates gradle task from command line.”
  4. The constraints or suggest fields (which you may encounter while exploring other examples) do not correspond with the UI’s autocomplete behavior. From combing through the Android Studio source, auto-complete is dictated on an exception basis by the control ID. If the id equals parentActivityClass, the UI autocompletes with the activity selection dialog. If the id equals packageName, the UI completes with the package selection dialog. Sadly, there is no way to autocomplete for class name.
  5. Copying from your repository files may appeal to you, since it would absolve the need for local syncing. Unfortunately, Android Studio’s implementation does not support this approach. It automatically defines a root folder and appends the from address to it, and consequently forbids copying/instantiating from your project directory. I made several attempts to get around this – and thus eliminate local syncing – with no luck.
  6. The Create from Template UI will throw an error if you attempt to create files that already exist in your project. This is a useful protection, but annoying during development.
  7. The bubble is a lifesaver, but be warned: it only shows up once per session. You’ll have to restart before the bubble shows up again.
  8. Here's a consolidated list of templating’s biggest flaws: no easy team collaboration, gathering of template data on startup, no auto-predict for selecting project classes, and the lack of project-specific awareness. Hypothetically, a custom IntelliJ plugin could address some of these concerns, but any developer intent on tackling these problems may consider contributing to Android Studio directly instead. A direct contribution ensures work would be put into an official solution, sanctioned by Google, available to everyone – and allowing you to become an Android Studio contributor.