TL;DR

I have created an example Android project you can use to kickstart your Java and/or Android annotation processing library based on the Gradle build system. It is also setup to easily publish the artifacts to JCenter (via Bintray) using the novoda/bintray-release plugin.




Ok, so I’ve read this article about annotation processing some time ago on Hannes Dorfmann’s blog. It’s a great article introducing you to the world of annotation processing in Java. It’s well worth reading! So make sure you check it out.

The example project that’s being created in that tutorial is using Maven as the build system. As an Android developer these days, your preferred companion for building your apps and/or libraries is (hopefully) Gradle. So let’s go ahead and see how we can produce a similar setup using the Gradle build system.




I’ve decided to create a simple annotation processor called StaticLauncher. The idea is that you can annotate any Activity with @StaticLauncher and it will generate a new ActivityLauncher class for you providing some handy, static helper methods to launch that Activity.

Here’s what it looks like - an annotated Activity:

@StaticLauncher
public class ActivityA extends ActionBarActivity {}

And the generated code:

public final class ActivityALauncher {
  public static Intent getIntent(Context context) {
    return new Intent(context, ActivityA.class);
  }

  public static void startActivity(Context context) {
    context.startActivity(getIntent(context));
  }
}

And that’s it - I guess it’s sufficient for a simple example. However, feel free to fork the repo and enhance the project to make it a truly useful library. For example, by adding support for Intent extras? :)

Ok, now this article is about the Gradle setup, so let’s have a look at the project structure:

As Hannes has already pointed out in his blog post, it’s quite common and best practice to separate out your custom annotations into a separate Java module. The main reason being that users of your library shouldn’t need to include all of your annotation processing logic and only use it for compilation.

Note that I usually name the 2nd module processor since it better describes its purpose. This time I called it compiler… because why not?

So, the contents of those modules are as follows:

api

  • Content: annotation definitions & potentially other, non-generated code
  • Dependencies: none

compiler

  • Content: annotation processing logic (generates the actual code)
  • Dependencies: api module & potentially other libraries that aid development (e.g. google/auto and square/javapoet)

sample

  • Content: the sample app (ba dum tss)
  • Dependencies: api module & compiler module as a compile time only dependency using hvisser/android-apt

For reference, here are the links to the modules’ build files:

I don’t think it’s worth going through every build file in detail. You can just checkout and browse the GitHub repo.




One more thing: If you’re using Java 8 as your SDK, you need to ensure and tell the compiler to build using Java 7 or you will get the following error during pre-dexing:

:sample:preDexDebug

UNEXPECTED TOP-LEVEL EXCEPTION:
com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
...while parsing com/jenzz/staticlauncher/StaticLauncher.class

1 error; aborting
Error:Execution failed for task ':sample:preDexDebug'.

To solve this, just put the following two lines into your api module’s build file and you’re ready to go:

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7




Finally, to publish your annotation processor to JCenter, all you need to do is modify the ext closure of the build.gradle file in the project root folder.

For this example project, it looks like this:

ext {
  userOrg = 'jenzz'
  groupId = 'com.jenzz.staticlauncher'
  uploadName = 'Android-StaticLauncher'
  publishVersion = '0.1'
  description = 'An example project of Java annotation processing based on Gradle'
  website = 'https://github.com/jenzz/Android-StaticLauncher'
  licences = ['MIT']
}

Both submodules reference these properties, so this is the only place you need to make changes. For more info about the available properties and other usages have a look at the plugin’s GitHub wiki.

And finally, here’s how to make it available to the world:

./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false