Android Annotation Processing Tutorial: Part 1: A Practical Approach

cover-android-annotation-processing-tutorial-part-1

Many famous libraries that you must have come across in Android development such as ButterKnife, Dagger2, Room, GreenDao, PlaceHolderView etc, use annotation processing.

The motivation for writing this tutorial:

There are a couple of good annotation processing tutorials and examples available on the internet but it was really difficult for me to search any reference that provided a complete solution, apart from the setup process. So, I decided to write this tutorial that aims to cover the following:

  1. Basics of annotation processing.
  2. Create a complete Android library similar to ButterKnife.
  3. Provide an introduction to JavaPoet.
  4. Explains the essentials for the created library.

Introduction

Annotation processing has become one of the most important language features in the modern Java programming. Java supports annotation processing from the release of Java 5 but its full potential has been realized in the recent years. Annotation processing in simple words is used to generate files during compilation.

Limitation: Annotation processing can only be used to generate new files and not to modify old ones.

Note: Annotation processing is not only used for writing .java files but all kinds of files. Example - metadata text files.

The real beauty is not in the fancy outer looks but in the simple within.

Let’s explore if it’s a fancy way to write code or it really does solve some problems that we encounter in software development.

Case Study :

Remove the codes that use Java Reflection APIs and make it safe from runtime exceptions

Reflection is a process in which we read a class and it’s member properties during runtime and try to modify these properties. This process though helps ous in creating a generic or implementation independent program but is also prone to lots of exceptions as we do not know the exact condition at the runtime. Class scanning and modification by reflection is a slow process and also an ugly way to isolate the code.

Example: Suppose we want to map a view object from XML to a Java class. Then by using reflection, we can do something similar to this.

  • Define an annotation BindView for mapping.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
} 
  • Place BindView annotation on a View class variable with the view's id.
public class MainActivity extends AppCompatActivity {
    ...
    @BindView(R.id.txtView)
    TextView txtView;
    ...
}
  • Create a class that does the assignment of the TextView object defined in the XML with the id tv_name to the variable tvName.
public class ViewBinder {
    /*
     * annotations for activity class
     * @param target is the activity with annotations
     */
    public static void bind(final Activity target){
        bindViews(target, target.getClass().getDeclaredFields(),
    }

    /*
     * initiate the view for the annotated public fields
     * @param obj is any class instance with annotations
     * @param fields list of methods in the class with annotation
     * @param rootView is the inflated view from the XML
     */
    private static void bindViews(final Object obj, Field[] fields, View rootView){
        for(final Field field : fields) {
            Annotation annotation = field.getAnnotation(BindView.class);
            if (annotation != null) {
                BindView bindView = (BindView) annotation;
                int id = bindView.value();
                View view = rootView.findViewById(id);
                try {
                    field.setAccessible(true);
                    field.set(obj, view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • Send the Activity instance to the ViewBinder .
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.txtView)
    private TextView txtView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBinder.bind(this);
        txtView.setText("Testing");
    }
    ...
}

This works fine but the limitation that we just talked about reflection make it undesirable.

So, how can we improve upon it?

This is an interesting question and the answer to this is the motivation for exploring the powerful APIs of annotation processing.

  1. We have to eliminate the runtime scan of the MainActivity class and replace it with method calls.
  2. We would not want to write those methods for every Activity and want them to get generated automatically.
  3. We would want to eliminate any runtime exceptions and like to move such checks during compile time itself.

These three conditions can be satisfied with the use of annotation processing.

So, are you excited to explore how to do this?

It is simpler than you might have heard. So, let's break the taboo and understand it.

The biggest hurdle in reaching the other side of the tunnel is not the task that is difficult but the lack of efforts. We are limited by the fear developed through the mouth of others.

How does the annotation processing work?

The annotation processing takes place in many compilation cycles. In each cycle, the compiler while reading a java source file finds the annotations registered for processing and calls the corresponding annotation processor. This cycle continues with the generation of any file or terminates if no file is generated in that cycle.

We will divide our learning process into 4 parts:

  1. Create an Android project for annotation processing.
  2. Understand the definition of annotations used for processing.
  3. Write a compiler module that does the code generation through annotation processing.
  4. Use the code generated through annotation processing.

After this tutorial, you will be able to write you own android library based on annotation processing.

Hard work and smart work are must for an exceptional life. It is in our hands to make it great or be in the crowd.

We will continue this tutorial in PART 2

Link to other parts of the tutorial:

  1. Part 2: The project structure
  2. Part 3: Generate Java source code
  3. Part 4:Use the generated code

The source code for this tutorial can be found here: https://github.com/MindorksOpenSource/annotation-processing-example

Thanks for reading this article. Be sure to share this article if you found it helpful. It would let others get this article and spread the knowledge.

Let’s become friends on Twitter, Linkedin, Github, and Facebook.

Learning is a journey, let’s learn together!