Writing code sometimes get repetitive. Writing out patterns like repository or MV* requires creating and linking together
lots of classes and resource files. Sometimes you stumble upon a great way of doing something, like creating static methods for starting activities or creating fragment instances.
Being consistent and remembering all these patterns can be hard, but Android templates are here to help!
Defining your own Android templates can be a great way of saving time creating creating the same boilerplate code or quickly generating a singleton skeleton that's thread safe and can't be instantiated through reflection.
What we will be creating
A template basically consists of xml and ftl files. Here we will create a template for creating a part of an app using MVP. The MVP structure will be based off the MVP structure in the Android Architecture Blueprints repository. The files our template will generate will be:
- An activity for creating the required classes
- A fragment serving as our view
- POJO classes for the model and presenter
- An interface contract
- Layout files and an option for selecting the layout generated
- A strings.xml file for this section of the app
- Adding the activity to our manifest
- Optional base interfaces and a util class.
Creating the template configuration
First off create a file structure matching the following, or download it here.
├── globals.xml.ftl
├── recipe.xml.ftl
├── root
└── template.xml
The root
folder will remain empty for now.
In the globals.xml.ftl
add the following
<?xml version="1.0"?>
<globals>
<global id="resOut" value="${resDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="manifestOut" value="${manifestDir}" />
</globals>
This defines the values resOut and srcOut, which we will use to place our generated template in the correct folders.
Now in the template.xml
add the following
<?xml version="1.0"?>
<template format="4"
revision="1"
name="Activity MVP"
description="Creates MVP classes - Activity, Presenter, Model and interfaces">
<category value="MyTemplates" />
<parameter id="className"
name="Name"
type="string"
constraints="class|unique|nonempty"
default=""
help="The shared name of the classes generated - Should start with a capital letter"/>
<parameter
id="layoutType"
name="Layout Type"
type="enum"
default="linearHor"
help="The type of layout to be included into the activity">
<option id="linearHor">LinearLayout Horizontal</option>
<option id="linearVer">LinearLayout Vertical</option>
<option id="relative">RelativeLayout</option>
<option id="frame">FrameLayout</option>
</parameter>
<parameter
id="addBaseInterfaces"
name="Base interfaces"
type="boolean"
default="false"
help="Check if you want the base interfaces to be created as well - Only needed once." />
<parameter
id="addActivityUtils"
name="Activity Utils"
type="boolean"
default="false"
help="Check if you want the ActivityUtils class added - Only needed once." />
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
This defines how the template is shown in Android Studio. The values format
, name
and description
are mandatory.
The category value creates a new group under New
section in Android Studio. This makes it easy to find your custom templates.
You can also specify as many parameters as you like. Here a name parameter is required, which is then used to name files and classes accordingly.
We also add options for which layout our fragment should be generated with and options to add the base interfaces or util class.
Finally the global variables are included and a recipe is added. We will get to that in a moment. First we need to create the template files for our code and layouts.
Code templates
Inside the root folder we place the templates for our code. Create the following file structure
├── AndroidManifest.xml.ftl
└── src
├── app_package
│ ├── Activity.java.ftl
│ ├── ActivityUtils.java.ftl
│ ├── Fragment.java.ftl
│ ├── IBasePresenter.java.ftl
│ ├── IBaseView.java.ftl
│ ├── IContract.java.ftl
│ ├── Model.java.ftl
│ └── Presenter.java.ftl
├── app_res
│ └── strings.xml.ftl
└── app_view
├── activity.xml.ftl
├── fragment_frame.xml.ftl
├── fragment_linear_hor.xml.ftl
├── fragment_linear_ver.xml.ftl
└── fragment_relative.xml.ftl
As you can see we are going to create a lot of files, so let's get started.
Android Manifest
Since we are adding an activity to our project we want the template to add the activity to our manifest.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${packageName}.${className}Activity"
android:label="@string/title_${className?lower_case}_activity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:windowSoftInputMode="adjustResize">
</activity>
</application>
</manifest>
The manifest template (AndroidManifest.xml.ftl
) should be placed in the root folder.
Here we can specify options we want all our activities to have, such as how the app should behave when the softkeyboard is displayed. Or which theme to use.
Base interfaces and utility class
The MVP example from Android Architecture Blueprints uses some base interfaces and a utility class. These classes needs to be added to the project for the files genereated by this template will compile. To make it easier to reuse this template in new projects I added options to add these files.
IBaseView.java.ftl
package ${packageName};
public interface IBaseView<T> {
void setPresenter(T presenter);
}
IBasePresenter.java.ftl
package ${packageName};
public interface IBasePresenter {
void start();
}
ActivityUtils.java.ftl
package ${packageName};
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
/**
* This provides methods to help Activities load their UI.
* Taken from https://github.com/googlesamples/android-architecture/blob/todo-mvp/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/util/ActivityUtils.java
*/
public class ActivityUtils {
/**
* The {@code fragment} is added to the container view with id {@code frameId}. The operation is
* performed by the {@code fragmentManager}.
*/
public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager,
@NonNull Fragment fragment, int frameId) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(frameId, fragment);
transaction.commit();
}
}
Java classes
For the java classes we need to create the activity, fragment, model, presenter, interfaces and utility class. The interfaces for the MVP classes will be placed in the same interface file, making it easier to keep track of them.
IContract.java.ftl
package ${packageName};
public interface I${className}Contract {
interface View extends IBaseView<Presenter> {
}
interface Presenter extends IBasePresenter {
}
interface Model {
}
}
Notice how our interfaces are extending the base interfaces. Moving on, creating the model class and implementing out empty model interface.
Model.java.ftl
package ${packageName};
public class ${className}Model implements I${className}Contract.Model {
public ${className}Model() {
}
}
The presenter should have a reference to the view and model interfaces. This follows the Dependency Inversion principle and makes it easier to test the presenter by allowing us to use mocks.
Presenter.java.ftl
package ${packageName};
public class ${className}Presenter implements I${className}Contract.Presenter {
private I${className}Contract.View view;
private I${className}Contract.Model model;
public ${className}Presenter(I${className}Contract.View view, I${className}Contract.Model model) {
this.view = view;
this.model = model;
this.view.setPresenter(this);
}
@Override
public void start() {
}
}
The view part of our MVP structure is implemented using a fragment. The view receives the presenter from the presenter itself and informs the presenter when the view is active.
Fragment.java.ftl
package ${packageName};
import android.support.v4.app.Fragment;
public class ${className}Fragment extends Fragment implements I${className}Contract.View {
private I${className}Contract.Presenter presenter;
public ${className}Fragment() {
super();
}
public static ${className}Fragment newInstance() {
return new ${className}Fragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_${className?lower_case}, container, false);
return view;
}
@Override
public void setPresenter(I${className}Contract.Presenter presenter) {
this.presenter = presenter;
}
@Override
public void onResume() {
super.onResume();
this.presenter.start();
}
}
Finally the activity which is responsible for instantiating and connecting the MVP.
Activity.java.ftl
package ${packageName};
import android.support.v7.widget.Toolbar;
public class ${className}Activity extends AppCompatActivity {
/**
* Starts the ${className} Activity
* @param context
*/
public static void start(Context context) {
Intent intent = new Intent(context, ${className}Activity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_${className?lower_case});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if(getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
//Check if fragment already exists
FrameLayout contentFrame = (FrameLayout) findViewById(R.id.content_frame);
${className}Fragment fragment =
(${className}Fragment) getSupportFragmentManager().findFragmentById(contentFrame.getId());
if (fragment == null) {
// Create the fragment
fragment = ${className}Fragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), fragment, contentFrame.getId());
}
// Create the presenter
new ${className}Presenter(
fragment,
new ${className}Model());
// Load previously saved state, if available.
if (savedInstanceState != null) {
}
}
}
Now that we have the Java part of the template wired up, we can start adding resource files.
Resources
In resources we are just going to add a new strings file, to keep the strings for this part of the app.
This makes it easier to find what you are looking for, helps keep order in larger projects and implement translations in smaller steps.
strings.xml.ftl
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="title_${className?lower_case}_activity">${className}</string>
</resources>
All we are doing is simply adding a name to our activity.
Layouts
Generating the layouts for the templates doesn't involve much configuration,
but because we are going to add the option of selecting which root layout should be generated we need to add a few files.
First of let's start with the layout for the activity. Since we might want to add elements like snackbars and floating action buttons, the activity should implement a coordinator layout. Then for the actual layout we are going to move it to a content layout file and include it into the activity.
activity.xml.ftl
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/${className?lower_case}_coordinator"
tools:context="${packageName}.${className}Activity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/fragment_${className?lower_case}" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
With the layout for the activity in place lets, create the various content layouts. We will allow the user to specifiy the following layouts
- LinearLayout (Vertical)
- LinearLayout (Horizontal)
- RelativeLayout
- FrameLayout
fragment_linear_ver.xml.ftl
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
fragment_linear_hor.xml.ftl
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
fragment_relative.xml.ftl
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
fragment_frame.xml.ftl
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
Doing the work
Remember the recipe file from earlier? Now that we have our code templates we can tell Android Studio what it should do with them. This is defined in the recipe file. Open the file and paste in the following content.
recipe.xml.ftl
<?xml version="1.0"?>
<recipe>
<!-- Manifest -->
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<!-- Classes -->
<instantiate from="src/app_package/IContract.java.ftl"
to="${escapeXmlAttribute(srcOut)}/I${className}Contract.java" />
<#if addBaseInterfaces == true>
<instantiate from="src/app_package/IBaseView.java.ftl"
to="${escapeXmlAttribute(srcOut)}/IBaseView.java" />
<instantiate from="src/app_package/IBasePresenter.java.ftl"
to="${escapeXmlAttribute(srcOut)}/IBasePresenter.java" />
</#if>
<#if addActivityUtils == true>
<instantiate from="src/app_package/ActivityUtils.java.ftl"
to="${escapeXmlAttribute(srcOut)}/ActivityUtils.java" />
</#if>
<instantiate from="src/app_package/Activity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}Activity.java" />
<instantiate from="src/app_package/Fragment.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}Fragment.java" />
<instantiate from="src/app_package/Model.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}Model.java" />
<instantiate from="src/app_package/Presenter.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}Presenter.java" />
<!-- Resources -->
<instantiate from="src/app_res/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings_${className?lower_case}.xml" />
<!-- Decide which layouts to add -->
<instantiate from="src/app_view/activity.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/activity_${className?lower_case}.xml" />
<#if layoutType == "linearHor">
<copy from="src/app_view/fragment_linear_hor.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/fragment_${className?lower_case}.xml" />
<#elseif layoutType == "linearVer">
<copy from="src/app_view/fragment_linear_ver.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/fragment_${className?lower_case}.xml" />
<#elseif layoutType == "relative">
<copy from="src/app_view/fragment_relative.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/fragment_${className?lower_case}.xml" />
<#elseif layoutType == "frame">
<copy from="src/app_view/fragment_frame.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/fragment_${className?lower_case}.xml" />
</#if>
<open file="${srcOut}/${className}Activity.java"/>
</recipe>
The recipe file defines what happens when the template is used.
First off we use the merge
command to merge our manifest with the app's, adding our activity. Then we are instantiating various templates. This triggers FreeMarker to run through our files, evaluate the expressions found and copy the result to the specified output files.
We check if we should add the base interfaces, utility class and which content layout file we should copy to the project. We also define which files we would like Android Studio to open, after the process has been completed. You can open as many files as you want. With this our template is complete and almost ready to be used!
Moving your files
In order for Android Studio to pickup your templates, you need to place them under YOUR_AS_PATH/plugins/android/lib/templates/YOUR_FOLDER
. Keep in mind, that if Android Studio is running, you need to restart it before you can see your templates.
I recommend you keep your templates versioned in a git repository. To copy the templates from your repository to Android Studio you can simply copy them, or use this rsync command from the parent folder where your templates are stored.
rsync -av --exclude='Readme.md' * YOUR_AS_PATH/plugins/android/lib/templates/YOUR_FOLDER
This allows you to exclude files like your Readme and so on.
Try it out
After moving your files, you need to restart Android Studio for it to load your templates. Then to use them
select New > MyTemplates > Activity MVP


This concludes the tutorial. You are now ready to create your own templates! If you want to have a look at the documentation for this, you can find it here.
I hope you enjoyed this tutorial, feel free to ask if you have any questions or just leave a comment.