A beginner's guide to Annotation Processing. We discuss how to implement a simplistic clone of the popular Android view binding library: ButterKnife, called "Soup Ladle"
an annotation, and what is annotation processing? • Why would I want to process annotations? • How do I make something cool? Maybe a ButterKnife clone?
• 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.
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
Processors Any Processors for them? Run Processors Compile No* Yes * 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
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.
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.
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.
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.
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 } }
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 } }
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 } }
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 } }
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 } }
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 } }
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 } }
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.
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.
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.
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.
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.
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.
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.
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.
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); } }