SlideShare a Scribd company logo
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 remotely)
• I have a dog named Watson. On weekends, we
walk across the Netherlands together.
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?
–http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html
“…an annotation is a form of syntactic
metadata…”
Annotations
–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
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.
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…)
Custom Annotations
• Useless
–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” (apt)
• Must be part of a plain-old java library, without
direct dependencies on Android-specific stuff.
• Extend from
javax.annotation.processing.AbstractProcessor
Annotation Processing
List unprocessed
source files with
annotations.
Register
Annotation
Processors
Any
Processors
for them?
Run ProcessorsCompile
No* Yes
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
Why would you want to make one?
• Boilerplate Reduction
• Reducing Boilerplate
• Reduced Boilerplate
• ….
• It’s pretty cool.
Let’s make one.
“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.
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.
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.
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.
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. 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.
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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.
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 ..
}
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 ..
}
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 ..
}
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 ..
}
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.
@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);

}
}
@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);

}
}
@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);

}
}
@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);

}
}
😱
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 classes needed for
import.
• When you’re ready, it will write clean & readable
Java source to an OutputStream/Writer.
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]);

}

}

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]);

}

}

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]);

}

}

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]);

}

}

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]);

}

}

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.
@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;
}
@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;
}
@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;
}
@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;
}
@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;
}
@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;
}
@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)"
@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)"
@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)"
@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)"
@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;
}
@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;
}
@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;
}
@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;
}
👀
@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;
}
@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;
}
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.
• Add the android-apt gradle plugin to your root
build.gradle.
• Add dependency records to your app’s
build.gradle.
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.
SoupLadle Module
SoupLadle Module


apply plugin: 'java'



dependencies {

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

compile 'com.squareup:javapoet:1.7.0'

}
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.
• Add the android-apt gradle plugin to your root
build.gradle.
• Add dependency records to your app’s
build.gradle.
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

}
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

}
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.
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')

}
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')

}
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')

}
Let’s try using it!
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>

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);

}

}
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);

}

}
rebuild project…
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);

}

}

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!");

}

}
Write code that writes code! A beginner's guide to Annotation Processing - Jason Feinstein
Write code that writes code! A beginner's guide to Annotation Processing - Jason Feinstein
Thank you! Questions?
Twitter: @jasonwyatt
github.com/jasonwyatt
bandcamp.com/jasonwyatt
Source Code available at:
github.com/jasonwyatt/Soup-Ladle

More Related Content

PDF
Build an App with Blindfold - Britt Barak
PDF
Engineering Wunderlist for Android - Ceasr Valiente, 6Wunderkinder
PDF
Think Async: Understanding the Complexity of Multithreading - Avi Kabizon & A...
PDF
Servlet and JSP
PPTX
Session And Cookies In Servlets - Java
PDF
Paris Tech Meetup talk : Troubles start at version 1.0
PDF
Akka lsug skills matter
PDF
Elements for an iOS Backend
Build an App with Blindfold - Britt Barak
Engineering Wunderlist for Android - Ceasr Valiente, 6Wunderkinder
Think Async: Understanding the Complexity of Multithreading - Avi Kabizon & A...
Servlet and JSP
Session And Cookies In Servlets - Java
Paris Tech Meetup talk : Troubles start at version 1.0
Akka lsug skills matter
Elements for an iOS Backend

What's hot (19)

PDF
Jsp & Ajax
PDF
Servlet sessions
PPTX
SenchaCon 2016: How to Auto Generate a Back-end in Minutes - Per Minborg, Emi...
PDF
Spring 4 Web App
PDF
Java EE 01-Servlets and Containers
PPTX
Dropwizard Internals
PPTX
Flask & Flask-restx
PPTX
Using MongoDB with the .Net Framework
PDF
Introduction to Retrofit and RxJava
PPTX
RxJS and Reactive Programming - Modern Web UI - May 2015
PPTX
Java web application development
ODP
Mongo db rev001.
PPTX
Servlets & jdbc
PPTX
ASP.Net 5 and C# 6
PPTX
Discovering the Service Fabric's actor model
DOCX
Jquery Ajax
PPTX
Academy PRO: HTML5 Data storage
PDF
JavaCro'14 - Building interactive web applications with Vaadin – Peter Lehto
Jsp & Ajax
Servlet sessions
SenchaCon 2016: How to Auto Generate a Back-end in Minutes - Per Minborg, Emi...
Spring 4 Web App
Java EE 01-Servlets and Containers
Dropwizard Internals
Flask & Flask-restx
Using MongoDB with the .Net Framework
Introduction to Retrofit and RxJava
RxJS and Reactive Programming - Modern Web UI - May 2015
Java web application development
Mongo db rev001.
Servlets & jdbc
ASP.Net 5 and C# 6
Discovering the Service Fabric's actor model
Jquery Ajax
Academy PRO: HTML5 Data storage
JavaCro'14 - Building interactive web applications with Vaadin – Peter Lehto
Ad

Viewers also liked (15)

PPTX
Will it run or will it not run? Background processes in Android 6 - Anna Lifs...
PDF
3 things every Android developer must know about Microsoft - Ido Volff, Micro...
PDF
Intro to Dependency Injection - Or bar
PDF
Android is going to Go! - Android and goland - Almog Baku
PPTX
Creating killer apps powered by watson cognitive services - Ronen Siman-Tov, IBM
PPTX
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
PDF
Cognitive interaction using Wearables - Eyal herman, IBM
PPTX
Good Rules for Bad Apps - Shem magnezi
PDF
Mobile SDKs: Use with Caution - Ori Lentzitzky
PDF
Android Application Optimization: Overview and Tools - Oref Barad, AVG
PDF
Context is Everything - Royi Benyossef
PPTX
Set it and forget it: Let the machine learn its job - Guy Baron, Vonage
PDF
Knock knock! Who's there? Doze. - Yonatan Levin
PPTX
Optimize your delivery and quality with the right release methodology and too...
PDF
Android Continuous Integration and Automation - Enrique Lopez Manas, Sixt
Will it run or will it not run? Background processes in Android 6 - Anna Lifs...
3 things every Android developer must know about Microsoft - Ido Volff, Micro...
Intro to Dependency Injection - Or bar
Android is going to Go! - Android and goland - Almog Baku
Creating killer apps powered by watson cognitive services - Ronen Siman-Tov, IBM
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Cognitive interaction using Wearables - Eyal herman, IBM
Good Rules for Bad Apps - Shem magnezi
Mobile SDKs: Use with Caution - Ori Lentzitzky
Android Application Optimization: Overview and Tools - Oref Barad, AVG
Context is Everything - Royi Benyossef
Set it and forget it: Let the machine learn its job - Guy Baron, Vonage
Knock knock! Who's there? Doze. - Yonatan Levin
Optimize your delivery and quality with the right release methodology and too...
Android Continuous Integration and Automation - Enrique Lopez Manas, Sixt
Ad

Similar to Write code that writes code! A beginner's guide to Annotation Processing - Jason Feinstein (20)

PPTX
JavaOne 2017 CON3282 - Code Generation with Annotation Processors: State of t...
PDF
Code transformation With Spoon
PDF
How to Reverse Engineer Web Applications
PPTX
JavaOne 2014 - CON2013 - Code Generation in the Java Compiler: Annotation Pro...
PDF
33rd degree talk: open and automatic coding conventions with walkmod
PPTX
Spring MVC framework
PPTX
Java Annotations
PPTX
Annotation processing
PDF
Free The Enterprise With Ruby & Master Your Own Domain
PDF
Refactoring In Tdd The Missing Part
PDF
Javascript classes and scoping
PPT
Java Basics
PDF
Cross-Platform Native Mobile Development with Eclipse
PDF
JDD 2016 - Grzegorz Rozniecki - Java 8 What Could Possibly Go Wrong
PDF
walkmod - JUG talk
PDF
Ejb3 Struts Tutorial En
PDF
Ejb3 Struts Tutorial En
ODP
Intro To Spring Python
PDF
Infinum Android Talks #02 - How to write an annotation processor in Android
ODP
Best practices tekx
JavaOne 2017 CON3282 - Code Generation with Annotation Processors: State of t...
Code transformation With Spoon
How to Reverse Engineer Web Applications
JavaOne 2014 - CON2013 - Code Generation in the Java Compiler: Annotation Pro...
33rd degree talk: open and automatic coding conventions with walkmod
Spring MVC framework
Java Annotations
Annotation processing
Free The Enterprise With Ruby & Master Your Own Domain
Refactoring In Tdd The Missing Part
Javascript classes and scoping
Java Basics
Cross-Platform Native Mobile Development with Eclipse
JDD 2016 - Grzegorz Rozniecki - Java 8 What Could Possibly Go Wrong
walkmod - JUG talk
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial En
Intro To Spring Python
Infinum Android Talks #02 - How to write an annotation processor in Android
Best practices tekx

More from DroidConTLV (20)

PDF
Mobile Development in the Information Age - Yossi Elkrief, Nike
PDF
Doing work in the background - Darryn Campbell, Zebra Technologies
PDF
No more video loss - Alex Rivkin, Motorola Solutions
PDF
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
PDF
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
PDF
MVVM In real life - Lea Cohen Tannoudji, Lightricks
PDF
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
PDF
Building Apps with Flutter - Hillel Coren, Invoice Ninja
PDF
New Android Project: The Most Important Decisions - Vasiliy Zukanov
PDF
Designing a Design System - Shai Mishali, Gett
PDF
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
PDF
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
PDF
Flutter State Management - Moti Bartov, Tikal
PDF
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
PDF
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
PDF
DroidconTLV 2019
PDF
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
PDF
Introduction to React Native - Lev Vidrak, Wix
PDF
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
PDF
Educating your app – adding ML edge to your apps - Maoz Tamir
Mobile Development in the Information Age - Yossi Elkrief, Nike
Doing work in the background - Darryn Campbell, Zebra Technologies
No more video loss - Alex Rivkin, Motorola Solutions
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
MVVM In real life - Lea Cohen Tannoudji, Lightricks
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Building Apps with Flutter - Hillel Coren, Invoice Ninja
New Android Project: The Most Important Decisions - Vasiliy Zukanov
Designing a Design System - Shai Mishali, Gett
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Flutter State Management - Moti Bartov, Tikal
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
DroidconTLV 2019
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Introduction to React Native - Lev Vidrak, Wix
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
Educating your app – adding ML edge to your apps - Maoz Tamir

Recently uploaded (20)

PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Encapsulation theory and applications.pdf
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PPT
Teaching material agriculture food technology
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Big Data Technologies - Introduction.pptx
PDF
Approach and Philosophy of On baking technology
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
NewMind AI Weekly Chronicles - August'25 Week I
Chapter 3 Spatial Domain Image Processing.pdf
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Encapsulation theory and applications.pdf
Network Security Unit 5.pdf for BCA BBA.
Per capita expenditure prediction using model stacking based on satellite ima...
MIND Revenue Release Quarter 2 2025 Press Release
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Teaching material agriculture food technology
Reach Out and Touch Someone: Haptics and Empathic Computing
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Programs and apps: productivity, graphics, security and other tools
MYSQL Presentation for SQL database connectivity
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Digital-Transformation-Roadmap-for-Companies.pptx
Big Data Technologies - Introduction.pptx
Approach and Philosophy of On baking technology
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx

Write code that writes code! A beginner's guide to Annotation Processing - Jason Feinstein

  • 1. Write code that writes code! A beginner’s guide to annotation processing.
  • 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. 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?
  • 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. 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.
  • 9. • Useless (until you actually use them…) Custom Annotations • Useless
  • 10. • Useless • Run-time - with reflection • Compile-time “Annotation Processor” • Useless (until you actually use them…) Custom Annotations • Useless
  • 11. –Everyone “Reflection is slow and you should never use it.”
  • 12. –Smart People “Reflection is slow and you should try to avoid using it on the main thread.”
  • 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. Annotation Processing List unprocessed source files with annotations. Register Annotation Processors Any Processors for them? Run ProcessorsCompile No* Yes
  • 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. Why would you want to make one? • Boilerplate Reduction • Reducing Boilerplate • Reduced Boilerplate • …. • It’s pretty cool.
  • 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. 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. 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. 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.
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. @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. @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. @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. @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. There has to be a better way!?
  • 46. There is a better way.
  • 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. 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. 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. 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. 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. 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. 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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @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. We’ve got an annotation processor now!
  • 73. How do we tell the build process about it?
  • 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. 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.
  • 77. SoupLadle Module 
 apply plugin: 'java'
 
 dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.squareup:javapoet:1.7.0'
 }
  • 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. 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. 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. 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. 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. 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. 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')
 }
  • 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. 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);
 }
 }
  • 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. 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!");
 }
 }
  • 96. Thank you! Questions? Twitter: @jasonwyatt github.com/jasonwyatt bandcamp.com/jasonwyatt Source Code available at: github.com/jasonwyatt/Soup-Ladle