Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Write code that writes code!

Write code that writes code!

A beginner's guide to annotation processing.

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

Jason Feinstein

September 24, 2016
Tweet

More Decks by Jason Feinstein

Other Decks in Technology

Transcript

  1. 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.
  2. 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?
  3. –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
  4. 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.
  5. • Useless • Run-time - with reflection • Compile-time “Annotation

    Processor” • Useless (until you actually use them…) Custom Annotations • Useless
  6. –Smart People “Reflection is slow and you should try to

    avoid using it on the main thread.”
  7. 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
  8. Annotation Processing List unprocessed source files with annotations. Register Annotation

    Processors Any Processors for them? Run Processors Compile No* Yes
  9. 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 Processors Compile No* Yes
  10. Why would you want to make one? • Boilerplate Reduction

    • Reducing Boilerplate • Reduced Boilerplate • …. • It’s pretty cool.
  11. “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.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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 } }
  17. 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 } }
  18. 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 } }
  19. 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 } }
  20. 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 } }
  21. 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 } }
  22. 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 } }
  23. 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.
  24. 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 .. }
  25. 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 .. }
  26. 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 .. }
  27. 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 .. }
  28. 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.
  29. @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);
 } }
  30. @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);
 } }
  31. @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);
 } }
  32. @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);
 } }
  33. 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.
  34. 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]);
 }
 }

  35. 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]);
 }
 }

  36. 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]);
 }
 }

  37. 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]);
 }
 }

  38. 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]);
 }
 }

  39. 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.
  40. @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; }
  41. @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; }
  42. @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; }
  43. @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; }
  44. @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; }
  45. @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; }
  46. @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)"
  47. @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)"
  48. @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)"
  49. @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)"
  50. @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; }
  51. @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; }
  52. @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; }
  53. @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; }
  54. @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; }
  55. @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; }
  56. 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.
  57. 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.
  58. SoupLadle Module 
 apply plugin: 'java'
 
 dependencies {
 compile

    fileTree(dir: 'libs', include: ['*.jar'])
 compile 'com.squareup:javapoet:1.7.0'
 }
  59. 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.
  60. 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
 }
  61. 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
 }
  62. 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.
  63. 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')
 }
  64. 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')
 }
  65. 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')
 }
  66. 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>

  67. 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);
 }
 }
  68. 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);
 }
 }
  69. 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);
 }
 }

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