Idiomatic Kotlin: Annotation Processor and Code Generation

Tompee Balauag
Familiar Android
Published in
4 min readMay 10, 2018
Photo by Andreas Gücklhorn on Unsplash

Note: This article is now a part of the Idiomatic Kotlin series. It was originally a 2-part series. The complete list is at the bottom of the article.

In this tutorial, we will learn how to use annotations to generate code. The github source can be found here.

Before we start, let us recap on what annotations are based on the last tutorial. Basically, they are metadata that you can attach in your code. Now, we introduce a new purpose. We can use annotations as instructions for the compiler. And we can create a consumer or a processor for the compiler to use for processing these annotations. Building on that concept, we need to create 2 entities, an annotation and an annotation processor.

Let us create an android project with 2 modules.

  • app — our application
  • annotations — annotation module
  • aprocessor — annotation processor module

Based on experience note: Since we are going to use the javax package, your modules should be a java library. Anything other than a java library will result in a compile error.

The hardest and trickiest part of all of these is the gradle configuration. I spent a lot of time resolving all the errors when I first started. So we will go through this one by one. We will start with the annotations module.

Based on experience note: The annotations should be separated from the processor. Not doing so will result in a Failed to find byte code error. I am not privy to the details of why it is happening but this is the only way that worked for me.

Above is the module configuration of annotations. Things to note are the java-library and the kotlin plugin. Now let us create an annotation class.

I forgot to mention that we will be creating a class annotation that can be used to generate a field-encapsulated version of it. We will conveniently call it Encapsulate.

Now onto the annotation processor.

Same as the annotations, we will be using the java-library and the kotlin plugin. We will also be using kotlin-kapt since we are going to use a special annotation processor that we will discuss later. Source compatibility should be set to your JVM version. For the dependencies, we need to compile the annotations project to be able for us to reference the annotations. The KotlinPoet and the AutoService will be discussed in a while.

Now let us define our own annotation processor.

First things first, we need to subclass AbstractProcessor. This is a convenience class that contains environment data that will be useful for us when generating code.

Subclassing the AbstractProcessor will require you to implement the process method. It may already be obvious but this is where you are going to process the annotations. But before going to processing, lets override 2 more functions, the getSupportedAnnotationTypes and the getSupportedSourceVersion. The first one accepts a list of your processor’s supported annotation and the latter accepts your supported source code version. I usually use the latest one.

Now onto the processing. The roundEnvironment is the annotation processing framework. You can use it to get the elements that you are interested in. In our case, we get all the elements annotated with Encapsulate. We first check if the instance is a class as interface and annotation classes are not allowed on our use case. Now, since we will generate source code, we need some way to write files and our actual code. That is where KotlinPoet comes in. It is a convenient library for generating kotlin source files. All language constructs are properly abstracted. Of course you can write the source code manually yourself but I suggest otherwise.

Our goal is to generate a class that will encapsulate the public fields of a class. I know this is not helpful in Kotlin because of property access syntax but we will still do it anyway just to achieve our purpose of demonstrating code generation.

The flow goes like this. If an element is a field, we will define a private property of the same type but is nullable and assign it to null. Then we will define a getter and setter function for each field. Simple right?

You need to pay attention to an environment option called kapt.kotlin.generated. This will translate to this path in your environment.

app\build\generated\source\kaptKotlin

If all goes according to plan, our processor should be able to generate a file named Encapsulated$classname in the path above. Well, not yet, really. The annotation processor should be explicitly registered to the compiler. To do that you have to create a META-INF file and add your processor’s fully qualified class name. But there is an easy and automated way of doing that. Annotating your class as an AutoService of type Processor will automatically register it. Neat and sweet.

Now let us configure our application.

What’s important here is the compileOptions. This should be similar to your JVM version. Then on the sourceSets, you have to add the autogenerated file path. Then in the dependencies, compile the annotations and the aprocessor via the kapt (since this is an annotation processor module). Now let us create a bare model and annotate it with @Encapsulate.

This model contains two public fields. Building our project will generate this class.

And you can use it now in your project. Boilerplate code no more.

Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.

  1. Extension Functions
  2. Sealed Classes
  3. Infix Functions
  4. Class Delegation
  5. Local functions
  6. Object and Singleton
  7. Sequences
  8. Lambdas and SAM constructors
  9. Lambdas with Receiver and DSL
  10. Elvis operator
  11. Property Delegates and Lazy
  12. Higher-order functions and Function Types
  13. Inline functions
  14. Lambdas and Control Flows
  15. Reified Parameters
  16. Noinline and Crossinline
  17. Variance
  18. Annotations and Reflection
  19. Annotation Processor and Code Generation

--

--