Annotation Processing in Android Studio

Aitor Viana
6 min readJul 18, 2016

--

Auto-code generation is a very powerful tool that every software engineer should use. It avoids coding that tedious and repetitive boilerplate that nobody should ever have to write, primarily because it normally is uninteresting code and highly prone to error.

For Java developers, there are several tools one could use to ease the pain of code generation. From APIs like Javapoet to the use of template-based solutions with Apache Velocity, they all pave the way for us to achieve our goal.

I will not discuss about any of these great tools or how to use them. I will however focus the post on how to use and process annotations to trigger code generation in Android Studio.

Annotations

I am sure you all have, consciously or unconsciously, used annotations.
Annotations are a class of metadata that can be associated with classes, methods, fields, parameters and even other annotations. They can be accessed at runtime — using reflection — and/or at compile time using annotation processors. Check out the following code.

public class Foo {
private Date mDate;
public Foo(@NonNull Date date){...};
...
}

You have all seen that kind of code before. And by now, you all know that Android Studio would complain if you were to call the constructor Foo with a null parameter. Well, you guessed it, Android Studio processes the @NonNull annotation to figure that out.
We are going to do just the same. We will go through the process of creating a custom annotation and its associated processor that can generate code at compile time, whenever a particular class is annotated with the custom annotation.

The ‘Process’ and the Annotation Processor

Before talking about the annotation processor, let’s have the big picture about what happens when we compile our Java application in Android Studio.
When building our application, Android Studio (among many other things) will build before that all the processor modules (pure Java code). After which, it will begin processing all annotations — including the custom ones we will define — in our Android project.
What each processor does when processing the annotation it has interest in, is entirely up to the processor.

Let’s say we want to develop a library that simplifies the task to parcel objects — yeah, I hate that boilerplate too.
And let’s say the library works just like many other libraries around, annotate the class you want to be parcelable and, the library will auto-code all the boilerplate for you. All we need is, a custom annotation and, the corresponding annotation processor — and all the magic to traverse the class fields, generate the Parcelable interface methods, yadda yadda yadda, that I will wisely ignore here :D

The Sample (skeleton) Code

The example comprises a simple app, a library that will contain our custom annotation(s) and the annotation processor (aka compiler). The base package will be named e.g. com.example.autoparcel.

The Custom Annotation

Create a Java module called library and, in there, create your custom annotation .
Let’s call it AutoParcel — because it will take POJOs and make them Parcelable and I am very good at naming things.

package com.example.autoparcel;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // on class level
@Retention(RetentionPolicy.SOURCE) // not needed at runtime
public @interface AutoParcel {}

Note that we can also annotate our custom annotation to (1) tell the framework that our annotation can only be used in classes and; (2) the annotation will be discarded by the compiler after it is processed by our annotation processor.

Let’s have a look at the library/build.gradle file too. We need to add source and target compatibility to Java 1.7. Because the annotation (library) will be used inside an Android project.

apply plugin: 'java'

// This module will be used in Android projects, need to be
// compatible with Java 1.7
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility =
JavaVersion.VERSION_1_7

dependencies {
...
}

The Annotation Processor

Now the fun part, the annotation processor.

Create the new Java module and call it e.g. compiler. Note that we don’t need this module to be an Android module because it will not be part of any Android application. It is just plain Java that will be called by the compiler to handle our custom annotation. We can even use Java versions higher than 1.7 when coding our processor.

Now create the processor class, let’s call it AutoParcelProcessor and place it inside a package dubbed e.g. com.example.autoparcel.codegen.

package com.example.autoparcel.codegen;@SupportedAnnotationTypes("com.example.autoparcel.AutoParcel")
public final class AutoParcelProcessor extends AbstractProcessor {
@Override
public boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment env) {
...
}
}

Few things to note here.

  1. The processor shall extend from AbstractProcessor class;
  2. It shall be annotated with fully qualified names of the annotations it will process and;
  3. It needs to implement the process() method. This method will be called during build to process the @AutoParcel annotation we created before and, where you will implement all the magic.

Tip: if you want to get the fully qualified name of a particular file in Android Studio, just righ-click on it and select Copy Reference.

I am not going to implement the full processor here. Just going to break it down to simple method calls to process all types annotated with @AutoParcel, and generate some Java file.

The method process() will return true when no other processor cares about any of its annotations, our case.

package com.example.autoparcel.codegen;...
@Override
public boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment env) {

Collection<? extends Element> annotatedElements =
env.getElementsAnnotatedWith(AutoParcel.class);
List<TypeElement> types =
new ImmutableList.Builder<TypeElement>()
.addAll(ElementFilter.typesIn(annotatedElements))
.build();

for (TypeElement type : types) {
processType(type);
}

// We are the only ones handling AutoParcel annotations
return true;
}
private void processType(TypeElement type) {
String className = generatedSubclassName(type);
String source = generateClass(type, className);
writeSourceFile(className, source, type);
}
private void writeSourceFile(
String className,
String text,
TypeElement originatingType) {
try {
JavaFileObject sourceFile =
processingEnv.getFiler().
createSourceFile(className, originatingType);
Writer writer = sourceFile.openWriter();
try {
writer.write(text);
} finally {
writer.close();
}
} catch (IOException e) {// silent}
}
...

Last bit, now that we have our processor, we need to tell the framework to use it. Create a javax Processor file. This is what framework compiler looks for to handle the all annotations. In Android Studio and for our example:

  1. create a resource folder under compiler/src/main/resources.
  2. under resources, create compiler/src/main/resources/META-INF.
  3. under META-INF, create compiler/src/main/resources/META-INF/services.
  4. under services, create the a file called javax.annotation.processing.Processor

The file shall contain the fully qualified names of each and every processor we’ve created.

com.example.autoparcel.codegen.AutoParcelProcessor

And that’s all there is to it. You’ve created your first annotation and annotation processor.

Usage

You’ve got this fat and, you have now a custom annotation and its processor to turn your boring POJOs into nice Parcelable objects, let’s put it to use then.

In your settings.gradle, just add both the modules.

include ':app', ':compiler', ':library'

In you app/build.gradle you just add both the library modulethat contains the custom annotation — and the compiler annotation processor module.

...
dependencies {
...
provided project(':library')
apt project(':compiler')
...
}

Note that I have included the library dependency using provided instead of compile. We can do that because as our annotation will be processed at compile time, we don’t needed inside our final application code.

Finally, use it inside your app. Start annotating your POJOs with the annotation and let the processor do the rest.

@AutoParcel
public class Foo{
...
}

I recommend using android-apt plugin to allow Android Studio working with annotation processors.

The Real (complete) Implementation

If you got this far, by now you should now be able (shame on me otherwise) to do annotation processing in Android Studio.
The example I used in this post comes somehow from a real library that makes objects Parcelable just annotating them. You can check out the library in this github repo.

--

--