Android templates

Android


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.

Warning!
When upgrading Android Studio your files will be marked as unknown. Simply delete the files and copy them back from your repository after installing the update.

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.