Blog
March 1, 2019Creating Custom Templates in Android Studio
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.
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.
/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.
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.
/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.
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:
- Your repo’s templates folder location is arbitrary. You can place it elsewhere, but doing so will invalidate several paths referenced throughout this article.
- 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.
- 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.”
- 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.
- 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.
- 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.
- 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.
- 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.