Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Write code that writes code!
A beginner’s guide to annotation processing.
Obligatory Speaker Details
• Software Engineer for Bandcamp
• I’m from the US, but am living in Europe for now.
(working r...
Questions we ask ourselves in the
beginning.
• What is an annotation, and what is annotation
processing?
• Why would I wan...
–http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html
“…an annotation is a form of syntactic
metadata…...
–http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html
“Annotations do not directly affect program
sema...
Annotations
• You’ve seen them before (e.g. @Override, @Deprecated, etc.)
• They allow you to decorate code with informati...
Custom annotations are
useless..
… until you use them.
Custom Annotations
• Useless
• Useless (until you actually use them…)
Custom Annotations
• Useless
• Useless
• Run-time - with reflection
• Compile-time “Annotation Processor”
• Useless (until you actually use them…)
Custo...
–Everyone
“Reflection is slow and you should never use it.”
–Smart People
“Reflection is slow and you should try to avoid
using it on the main thread.”
Annotation Processors
• Operate at build-time, rather than run-time.
• Are executed by the “annotation processing
tool” (a...
Annotation Processing
List unprocessed
source files with
annotations.
Register
Annotation
Processors
Any
Processors
for the...
Annotation Processing
* If a processor was asked to process on a given round, it will be asked to process on
subsequent ro...
Why would you want to make one?
• Boilerplate Reduction
• Reducing Boilerplate
• Reduced Boilerplate
• ….
• It’s pretty co...
Let’s make one.
“Soup Ladle”
• Wanted something that sounded like Butter
Knife, but was a different utensil.
• I like Soup.
• Ladles are b...
Soup Ladle Goals
• Allow for view binding with an annotation:

@Bind(R.id.some_id) View fieldName;
• Perform the binding e...
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
Define @Bind
@Target(ElementType.FIELD)

@Retention(RetentionPolicy.SOURCE)

public @interface Bind {

int value();

}
Define @Bind
@Target(ElementType.FIELD)

@Retention(RetentionPolicy.SOURCE)

public @interface Bind {

int value();

}
Define @Bind
@Target(ElementType.FIELD)

@Retention(RetentionPolicy.SOURCE)

public @interface Bind {

int value();

}
Define @Bind
@Target(ElementType.FIELD)

@Retention(RetentionPolicy.SOURCE)

public @interface Bind {

int value();

}
Define @Bind
@Target(ElementType.FIELD)

@Retention(RetentionPolicy.SOURCE)

public @interface Bind {

int value();

}
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Extending AbstractProcessor
public class AnnotationProcessor extends AbstractProcessor {

private Filer mFiler;



@Overri...
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
Processing…
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (an...
Processing…
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (an...
Processing…
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (an...
Processing…
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (an...
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
There has to be a better way!?
There is a better way.
Introducing: JavaPoet
Introducing: JavaPoet
By Square (Of Course)
JavaPoet
• Builder-pattern approach to programmatically
defining a class and its fields/methods.
• Automatically manages the...
JavaPoet - Hello World
TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC)

...
JavaPoet - Hello World
TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC)

...
JavaPoet - Hello World
TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC)

...
JavaPoet - Hello World
TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC)

...
JavaPoet - Hello World
👏
TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC)...
Approach
1. Define the @Bind annotation.
2. Extend AbstractProcessor to create our
annotation processor for @Bind.
3. Withi...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

// .. process anno...
We’ve got an annotation
processor now!
How do we tell the build
process about it?
Project/Module Config
• The annotation processor and binding
annotation class need to live in a “regular” java
module.
• Ad...
Project/Module Config
• The annotation processor and binding
annotation class need to live in a “regular” java
module.
• Ad...
SoupLadle Module
SoupLadle Module


apply plugin: 'java'



dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

compile 'com...
SoupLadle Module
jwf.soupladle.AnnotationProcessor
SoupLadle Module
include ':app', ':library'
Project/Module Config
• The annotation processor and binding
annotation class need to live in a “regular” java
module.
• Ad...
Project build.gradle
buildscript {

repositories {

jcenter()

}

dependencies {

classpath 'com.android.tools.build:gradl...
Project build.gradle
buildscript {

repositories {

jcenter()

}

dependencies {

classpath 'com.android.tools.build:gradl...
Project/Module Config
• The annotation processor and binding
annotation class need to live in a “regular” java
module.
• Ad...
App build.gradle
apply plugin: 'com.android.application'

apply plugin: 'com.neenbedankt.android-apt'



android {

compil...
App build.gradle
apply plugin: 'com.android.application'

apply plugin: 'com.neenbedankt.android-apt'



android {

compil...
App build.gradle
apply plugin: 'com.android.application'

apply plugin: 'com.neenbedankt.android-apt'



android {

compil...
Let’s try using it!
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

xmlns:android="http://schemas.android.com/apk/r...
MainActivity.java
package jwf.soupladle.example;



import android.support.v7.app.AppCompatActivity;

import android.os.Bu...
MainActivity.java
package jwf.soupladle.example;



import android.support.v7.app.AppCompatActivity;

import android.os.Bu...
rebuild project…
A wild SoupLadle.java Appears!
package jwf.soupladle;



import android.widget.TextView;

import java.lang.SuppressWarning...
MainActivity.java
package jwf.soupladle.example;



import android.support.v7.app.AppCompatActivity;

import android.os.Bu...
Thank you! Questions?
Twitter: @jasonwyatt
github.com/jasonwyatt
bandcamp.com/jasonwyatt
Source Code available at:
github....
Write code that writes code!
Write code that writes code!
Upcoming SlideShare
Loading in …5
×

Write code that writes code!

59 views

Published on

A beginner's guide to annotation processing.

In this talk that I gave at Droidcon Tel Aviv in 2016, I walk you through the process of building a custom annotation processor which mimics some of the behavior you may be familiar with from the popular Android library: Butter Knife.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Write code that writes code!

  1. 1. Write code that writes code! A beginner’s guide to annotation processing.
  2. 2. Obligatory Speaker Details • Software Engineer for Bandcamp • I’m from the US, but am living in Europe for now. (working remotely) • I have a dog named Watson. On weekends, we walk across the Netherlands together.
  3. 3. Questions we ask ourselves in the beginning. • What is an annotation, and what is annotation processing? • Why would I want to process annotations? • How do I make something cool? Maybe a ButterKnife clone?
  4. 4. –http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html “…an annotation is a form of syntactic metadata…” Annotations
  5. 5. –http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html “Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program.” Annotations
  6. 6. Annotations • You’ve seen them before (e.g. @Override, @Deprecated, etc.) • They allow you to decorate code with information about the code (ie: they are meta data) • Kind of like comments, but they are more machine readable than human readable. • Annotations can be used by the JDK, third party libraries, or custom tools. • You can create your own annotations.
  7. 7. Custom annotations are useless.. … until you use them.
  8. 8. Custom Annotations • Useless
  9. 9. • Useless (until you actually use them…) Custom Annotations • Useless
  10. 10. • Useless • Run-time - with reflection • Compile-time “Annotation Processor” • Useless (until you actually use them…) Custom Annotations • Useless
  11. 11. –Everyone “Reflection is slow and you should never use it.”
  12. 12. –Smart People “Reflection is slow and you should try to avoid using it on the main thread.”
  13. 13. Annotation Processors • Operate at build-time, rather than run-time. • Are executed by the “annotation processing tool” (apt) • Must be part of a plain-old java library, without direct dependencies on Android-specific stuff. • Extend from javax.annotation.processing.AbstractProcessor
  14. 14. Annotation Processing List unprocessed source files with annotations. Register Annotation Processors Any Processors for them? Run ProcessorsCompile No* Yes
  15. 15. Annotation Processing * If a processor was asked to process on a given round, it will be asked to process on subsequent rounds, including the last round, even if there are no annotations for it to process. 
 
 https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html List unprocessed source files with annotations. Register Annotation Processors Any Processors for them? Run ProcessorsCompile No* Yes
  16. 16. Why would you want to make one? • Boilerplate Reduction • Reducing Boilerplate • Reduced Boilerplate • …. • It’s pretty cool.
  17. 17. Let’s make one.
  18. 18. “Soup Ladle” • Wanted something that sounded like Butter Knife, but was a different utensil. • I like Soup. • Ladles are big spoons. • Big spoon = more soup in my face at once.
  19. 19. Soup Ladle Goals • Allow for view binding with an annotation:
 @Bind(R.id.some_id) View fieldName; • Perform the binding easily using a one liner in onCreate:
 SoupLadle.bind(this); • That’s it.. we are reinventing the wheel for learning’s sake and don’t need to go all in.
  20. 20. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  21. 21. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  22. 22. Define @Bind @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Bind {
 int value();
 }
  23. 23. Define @Bind @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Bind {
 int value();
 }
  24. 24. Define @Bind @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Bind {
 int value();
 }
  25. 25. Define @Bind @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Bind {
 int value();
 }
  26. 26. Define @Bind @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Bind {
 int value();
 }
  27. 27. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  28. 28. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  29. 29. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  30. 30. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  31. 31. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  32. 32. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  33. 33. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  34. 34. Extending AbstractProcessor public class AnnotationProcessor extends AbstractProcessor {
 private Filer mFiler;
 
 @Override
 public synchronized void init(ProcessingEnvironment processingEnv) {
 super.init(processingEnv);
 mFiler = processingEnv.getFiler();
 }
 
 @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
 
 @Override
 public Set<String> getSupportedAnnotationTypes() {
 HashSet<String> result = new HashSet<>();
 result.add(Bind.class.getCanonicalName());
 return result;
 }
 
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // logic goes here // as does the actual code generation // // we’ll get to this stuff in a little bit } }
  35. 35. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  36. 36. Processing… @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return true;
 }
 
 Map<TypeElement, List<VariableElement>> bindingClasses = new HashMap<>();
 for (Element e : roundEnv.getElementsAnnotatedWith(Bind.class)) {
 VariableElement variable = (VariableElement) e;
 TypeElement parent = (TypeElement) variable.getEnclosingElement();
 
 List<VariableElement> members;
 if (bindingClasses.containsKey(parentClass)) {
 members = bindingClasses.get(parentClass);
 } else {
 members = new ArrayList<>();
 bindingClasses.put(parentClass, members);
 }
 members.add(variable);
 } // .. generate code .. }
  37. 37. Processing… @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return true;
 }
 
 Map<TypeElement, List<VariableElement>> bindingClasses = new HashMap<>();
 for (Element e : roundEnv.getElementsAnnotatedWith(Bind.class)) {
 VariableElement variable = (VariableElement) e;
 TypeElement parent = (TypeElement) variable.getEnclosingElement();
 
 List<VariableElement> members;
 if (bindingClasses.containsKey(parentClass)) {
 members = bindingClasses.get(parentClass);
 } else {
 members = new ArrayList<>();
 bindingClasses.put(parentClass, members);
 }
 members.add(variable);
 } // .. generate code .. }
  38. 38. Processing… @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return true;
 }
 
 Map<TypeElement, List<VariableElement>> bindingClasses = new HashMap<>();
 for (Element e : roundEnv.getElementsAnnotatedWith(Bind.class)) {
 VariableElement variable = (VariableElement) e;
 TypeElement parent = (TypeElement) variable.getEnclosingElement();
 
 List<VariableElement> members;
 if (bindingClasses.containsKey(parentClass)) {
 members = bindingClasses.get(parentClass);
 } else {
 members = new ArrayList<>();
 bindingClasses.put(parentClass, members);
 }
 members.add(variable);
 } // .. generate code .. }
  39. 39. Processing… @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return true;
 }
 
 Map<TypeElement, List<VariableElement>> bindingClasses = new HashMap<>();
 for (Element e : roundEnv.getElementsAnnotatedWith(Bind.class)) {
 VariableElement variable = (VariableElement) e;
 TypeElement parent = (TypeElement) variable.getEnclosingElement();
 
 List<VariableElement> members;
 if (bindingClasses.containsKey(parentClass)) {
 members = bindingClasses.get(parentClass);
 } else {
 members = new ArrayList<>();
 bindingClasses.put(parentClass, members);
 }
 members.add(variable);
 } // .. generate code .. }
  40. 40. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  41. 41. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 } catch (IOException e) {
 throw new RuntimeException(e);
 } }
  42. 42. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 } catch (IOException e) {
 throw new RuntimeException(e);
 } }
  43. 43. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 } catch (IOException e) {
 throw new RuntimeException(e);
 } }
  44. 44. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 } catch (IOException e) {
 throw new RuntimeException(e);
 } } 😱
  45. 45. There has to be a better way!?
  46. 46. There is a better way.
  47. 47. Introducing: JavaPoet
  48. 48. Introducing: JavaPoet By Square (Of Course)
  49. 49. JavaPoet • Builder-pattern approach to programmatically defining a class and its fields/methods. • Automatically manages the classes needed for import. • When you’re ready, it will write clean & readable Java source to an OutputStream/Writer.
  50. 50. JavaPoet - Hello World TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")
 .addModifiers(Modifier.PUBLIC)
 .addMethod(MethodSpec.methodBuilder("main")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .returns(void.class)
 .addParameter(String[].class, "args")
 .addStatement("System.out.println($S + args[0])", "Hello: ")
 .build());
 JavaFile.builder("jwf.soupladle", helloWorld.build()).build().writeTo(System.out); package jwf.soupladle;
 
 import java.lang.String;
 
 public class HelloWorld {
 public static void main(String[] args) {
 System.out.println("Hello: " + args[0]);
 }
 }

  51. 51. JavaPoet - Hello World TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")
 .addModifiers(Modifier.PUBLIC)
 .addMethod(MethodSpec.methodBuilder("main")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .returns(void.class)
 .addParameter(String[].class, "args")
 .addStatement("System.out.println($S + args[0])", "Hello: ")
 .build());
 JavaFile.builder("jwf.soupladle", helloWorld.build()).build().writeTo(System.out); package jwf.soupladle;
 
 import java.lang.String;
 
 public class HelloWorld {
 public static void main(String[] args) {
 System.out.println("Hello: " + args[0]);
 }
 }

  52. 52. JavaPoet - Hello World TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")
 .addModifiers(Modifier.PUBLIC)
 .addMethod(MethodSpec.methodBuilder("main")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .returns(void.class)
 .addParameter(String[].class, "args")
 .addStatement("System.out.println($S + args[0])", "Hello: ")
 .build());
 JavaFile.builder("jwf.soupladle", helloWorld.build()).build().writeTo(System.out); package jwf.soupladle;
 
 import java.lang.String;
 
 public class HelloWorld {
 public static void main(String[] args) {
 System.out.println("Hello: " + args[0]);
 }
 }

  53. 53. JavaPoet - Hello World TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")
 .addModifiers(Modifier.PUBLIC)
 .addMethod(MethodSpec.methodBuilder("main")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .returns(void.class)
 .addParameter(String[].class, "args")
 .addStatement("System.out.println($S + args[0])", "Hello: ")
 .build());
 JavaFile.builder("jwf.soupladle", helloWorld.build()).build().writeTo(System.out); package jwf.soupladle;
 
 import java.lang.String;
 
 public class HelloWorld {
 public static void main(String[] args) {
 System.out.println("Hello: " + args[0]);
 }
 }

  54. 54. JavaPoet - Hello World 👏 TypeSpec.Builder helloWorld = TypeSpec.classBuilder("HelloWorld")
 .addModifiers(Modifier.PUBLIC)
 .addMethod(MethodSpec.methodBuilder("main")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .returns(void.class)
 .addParameter(String[].class, "args")
 .addStatement("System.out.println($S + args[0])", "Hello: ")
 .build());
 JavaFile.builder("jwf.soupladle", helloWorld.build()).build().writeTo(System.out); package jwf.soupladle;
 
 import java.lang.String;
 
 public class HelloWorld {
 public static void main(String[] args) {
 System.out.println("Hello: " + args[0]);
 }
 }

  55. 55. Approach 1. Define the @Bind annotation. 2. Extend AbstractProcessor to create our annotation processor for @Bind. 3. Within our processor: scan for all fields with @Bind, keeping track of their parent classes. 4. Generate SoupLadle.java with .bind methods for each parent class containing bound fields.
  56. 56. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  57. 57. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  58. 58. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  59. 59. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  60. 60. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  61. 61. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  62. 62. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; } "target.$L = ($T) target.findViewById($L)"
  63. 63. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; } "target.$L = ($T) target.findViewById($L)"
  64. 64. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; } "target.$L = ($T) target.findViewById($L)"
  65. 65. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; } "target.$L = ($T) target.findViewById($L)"
  66. 66. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  67. 67. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  68. 68. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  69. 69. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; } 👀
  70. 70. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  71. 71. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 // .. process annotations .. try {
 JavaFileObject jfo = mFiler.createSourceFile("jwf.soupladle.SoupLadle");
 
 TypeSpec.Builder soupLadleBuilder = TypeSpec.classBuilder("SoupLadle")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
 
 for (Map.Entry<TypeElement, List<VariableElement>> binding : bindingClasses.entrySet()) {
 TypeName typeParameter = ClassName.get(binding.getKey());
 
 MethodSpec.Builder bindingBuilder = MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
 .addParameter(typeParameter, "target");
 
 List<VariableElement> members = binding.getValue();
 for (VariableElement member : members) {
 Bind annotation = member.getAnnotation(Bind.class);
 TypeName castClass = ClassName.get(member.asType());
 bindingBuilder.addStatement("target.$L = ($T) target.findViewById($L)", member.getSimpleName().toString(), castClass, annotation.value());
 }
 
 soupLadleBuilder.addMethod(bindingBuilder.build());
 }
 
 AnnotationSpec suppressIdentifierWarningAnnotation = AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType").build(); soupLadleBuilder.addAnnotation(suppressIdentifierWarningAnnotation); 
 JavaFile file = JavaFile.builder("jwf.soupladle", soupLadleBuilder.build()).build();
 Writer out = jfo.openWriter();
 file.writeTo(out);
 out.flush();
 out.close();
 } catch (IOException e) {
 throw new RuntimeException(e);
 } return true; }
  72. 72. We’ve got an annotation processor now!
  73. 73. How do we tell the build process about it?
  74. 74. Project/Module Config • The annotation processor and binding annotation class need to live in a “regular” java module. • Add the android-apt gradle plugin to your root build.gradle. • Add dependency records to your app’s build.gradle.
  75. 75. Project/Module Config • The annotation processor and binding annotation class need to live in a “regular” java module. • Add the android-apt gradle plugin to your root build.gradle. • Add dependency records to your app’s build.gradle.
  76. 76. SoupLadle Module
  77. 77. SoupLadle Module 
 apply plugin: 'java'
 
 dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.squareup:javapoet:1.7.0'
 }
  78. 78. SoupLadle Module jwf.soupladle.AnnotationProcessor
  79. 79. SoupLadle Module include ':app', ':library'
  80. 80. Project/Module Config • The annotation processor and binding annotation class need to live in a “regular” java module. • Add the android-apt gradle plugin to your root build.gradle. • Add dependency records to your app’s build.gradle.
  81. 81. Project build.gradle buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:2.1.3'
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 }
 }
 
 allprojects {
 repositories {
 jcenter()
 }
 }
 
 task clean(type: Delete) {
 delete rootProject.buildDir
 }
  82. 82. Project build.gradle buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:2.1.3'
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 }
 }
 
 allprojects {
 repositories {
 jcenter()
 }
 }
 
 task clean(type: Delete) {
 delete rootProject.buildDir
 }
  83. 83. Project/Module Config • The annotation processor and binding annotation class need to live in a “regular” java module. • Add the android-apt gradle plugin to your root build.gradle. • Add dependency records to your app’s build.gradle.
  84. 84. App build.gradle apply plugin: 'com.android.application'
 apply plugin: 'com.neenbedankt.android-apt'
 
 android {
 compileSdkVersion 24
 buildToolsVersion "24.0.2"
 
 defaultConfig {
 applicationId "jwf.soupladle.example"
 minSdkVersion 16
 targetSdkVersion 24
 versionCode 1
 versionName "1.0"
 }
 buildTypes {
 // .. your build types ..
 }
 }
 
 dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.android.support:appcompat-v7:24.1.1'
 
 apt project(':library')
 provided project(':library')
 }
  85. 85. App build.gradle apply plugin: 'com.android.application'
 apply plugin: 'com.neenbedankt.android-apt'
 
 android {
 compileSdkVersion 24
 buildToolsVersion "24.0.2"
 
 defaultConfig {
 applicationId "jwf.soupladle.example"
 minSdkVersion 16
 targetSdkVersion 24
 versionCode 1
 versionName "1.0"
 }
 buildTypes {
 // .. your build types ..
 }
 }
 
 dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.android.support:appcompat-v7:24.1.1'
 
 apt project(':library')
 provided project(':library')
 }
  86. 86. App build.gradle apply plugin: 'com.android.application'
 apply plugin: 'com.neenbedankt.android-apt'
 
 android {
 compileSdkVersion 24
 buildToolsVersion "24.0.2"
 
 defaultConfig {
 applicationId "jwf.soupladle.example"
 minSdkVersion 16
 targetSdkVersion 24
 versionCode 1
 versionName "1.0"
 }
 buildTypes {
 // .. your build types ..
 }
 }
 
 dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.android.support:appcompat-v7:24.1.1'
 
 apt project(':library')
 provided project(':library')
 }
  87. 87. Let’s try using it!
  88. 88. activity_main.xml <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 tools:context="jwf.soupladle.example.MainActivity">
 
 <TextView
 android:id="@+id/hello_world"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="Hello World!"/>
 </RelativeLayout>

  89. 89. MainActivity.java package jwf.soupladle.example;
 
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.widget.TextView;
 
 import jwf.soupladle.Bind;
 
 public class MainActivity extends AppCompatActivity {
 @Bind(R.id.hello_world)
 public TextView textView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 }
  90. 90. MainActivity.java package jwf.soupladle.example;
 
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.widget.TextView;
 
 import jwf.soupladle.Bind;
 
 public class MainActivity extends AppCompatActivity {
 @Bind(R.id.hello_world)
 public TextView textView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 }
  91. 91. rebuild project…
  92. 92. A wild SoupLadle.java Appears! package jwf.soupladle;
 
 import android.widget.TextView;
 import java.lang.SuppressWarnings;
 import jwf.soupladle.example.MainActivity;
 
 @SuppressWarnings("ResourceType")
 public final class SoupLadle {
 public static final void bind(MainActivity target) {
 target.textView = (TextView) target.findViewById(2131427412);
 }
 }

  93. 93. MainActivity.java package jwf.soupladle.example;
 
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.widget.TextView;
 
 import jwf.soupladle.Bind;
 import jwf.soupladle.SoupLadle;
 
 public class MainActivity extends AppCompatActivity {
 @Bind(R.id.hello_world)
 public TextView textView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 SoupLadle.bind(this);
 textView.setText("The binding worked!");
 }
 }
  94. 94. Thank you! Questions? Twitter: @jasonwyatt github.com/jasonwyatt bandcamp.com/jasonwyatt Source Code available at: github.com/jasonwyatt/Soup-Ladle

×