Lightning Talk: Android Annotation Processors

Lightning Talk: Android Annotation Processors

An overview of annotations and annotation processors, focused on Android. Presented at the Philly Android Alliance and Philly Cocoaheads joint meetup as part of Philly Tech Week 2017.

7d1513d777e2134ef8113e60b16ebf88?s=128

Audrey Troutt

May 04, 2017
Tweet

Transcript

  1. @auditty +AudreyTroutt Android Annotation Processors Audrey Troutt

  2. @auditty +AudreyTroutt @Override // An annotation! protected void onResume() {

    super.onResume(); … } Have you used them? Yes, you have
  3. @auditty +AudreyTroutt Why use Annotations? // BEFORE class AndroidWay extends

    Activity { TextView name; ImageView thumbnail; LocationManager loc; Drawable icon; String myName; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (TextView) findViewById(R.id.name); thumbnail = (ImageView) findViewById(R.id.thumbnail); loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE); icon = getResources().getDrawable(R.drawable.icon); myName = getString(R.string.app_name); name.setText( "Hello, " + myName ); } } // AFTER @ContentView(R.layout.main) class RoboWay extends RoboActivity { @InjectView(R.id.name) TextView name; @InjectView(R.id.thumbnail) ImageView thumbnail; @InjectResource(R.drawable.icon) Drawable icon; @InjectResource(R.string.app_name) String myName; @Inject LocationManager loc; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); name.setText( "Hello, " + myName ); } } // example borrowed from RoboGuice :) https://github.com/roboguice/roboguice/wiki
  4. @auditty +AudreyTroutt Why use Annotations? // BEFORE class AndroidWay extends

    Activity { TextView name; ImageView thumbnail; LocationManager loc; Drawable icon; String myName; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (TextView) findViewById(R.id.name); thumbnail = (ImageView) findViewById(R.id.thumbnail); loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE); icon = getResources().getDrawable(R.drawable.icon); myName = getString(R.string.app_name); name.setText( "Hello, " + myName ); } } // AFTER @ContentView(R.layout.main) class RoboWay extends RoboActivity { @InjectView(R.id.name) TextView name; @InjectView(R.id.thumbnail) ImageView thumbnail; @InjectResource(R.drawable.icon) Drawable icon; @InjectResource(R.string.app_name) String myName; @Inject LocationManager loc; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); name.setText( "Hello, " + myName ); } } // example borrowed from RoboGuice :) https://github.com/roboguice/roboguice/wiki
  5. @auditty +AudreyTroutt Who creates Annotations? • Library developers, mostly

  6. @auditty +AudreyTroutt #import "UILabel+TextMagic.h" // This is a category @implementation

    UILabel (TextMagic) - (CGFloat)bottomYCoordinate { return self.frame.origin.y + self.frame.size.height; } - (CGFloat)rightXCoordinate { return self.frame.origin.x + self.frame.size.width; } @end // from https://github.com/ArtisanMobile/ArtisanCategoryMagic Metaprogramming is easy* on iOS
  7. @auditty +AudreyTroutt #import <objc/runtime.h> // This is method swizzling +

    (void)replaceOriginalInstanceMethod:(SEL)originalMethod from:(id)original withInstanceMethod:(SEL)replacementMethod from:(id)replacementObject { Method originalMethod = class_getInstanceMethod([original class], originalMethod); Method mockMethod = class_getInstanceMethod([replacementObject class], replacementMethod); method_exchangeImplementations(originalMethod, mockMethod); } // Don’t do this in production code! It’s more polite to always call the original code. Metaprogramming is easy* on iOS
  8. @auditty +AudreyTroutt Annotations • Define metadata about code

  9. @auditty +AudreyTroutt Annotation Processing https://medium.com/@lgvalle/how-butterknife-actually-works-85be0afbc5ab

  10. @auditty +AudreyTroutt 1. Annotation(s) ◦ are interfaces 2. Processor(s) ◦

    extend javax.annotation.processing.AbstractProces sor 3. Additional API to leverage generated code Annotation Processor Project Parts
  11. @auditty +AudreyTroutt

  12. @auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod {

    String eventName(); String logProperty() default ""; } Annotation
  13. @auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod {

    String eventName(); String logProperty() default ""; } Annotation
  14. @auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod {

    String eventName(); String logProperty() default ""; } Annotation
  15. @auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod {

    String eventName(); String logProperty() default ""; } Annotation
  16. @auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod {

    String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotation
  17. @auditty +AudreyTroutt Annotation Processor import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment;

    import javax.lang.model.element.TypeElement; public class WatchedMethodProcessor extends AbstractProcessor { … // where all the processing happens @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { return false; } }
  18. @auditty +AudreyTroutt Annotation Processor import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment;

    import javax.lang.model.element.TypeElement; public class WatchedMethodProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton("com.audreytroutt.WatchedMethod"); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } ...
  19. @auditty +AudreyTroutt Annotation Processor import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment;

    import javax.lang.model.element.TypeElement; public class WatchedMethodProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton("com.audreytroutt.WatchedMethod"); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } ...
  20. @auditty +AudreyTroutt Annotation Processor import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment;

    import javax.lang.model.element.TypeElement; public class WatchedMethodProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton("com.audreytroutt.WatchedMethod"); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } ...
  21. @auditty +AudreyTroutt Annotation Processor package javax.annotation.processing; public abstract class AbstractProcessor

    implements Processor { public Set<String> getSupportedAnnotationTypes() { ... } public SourceVersion getSupportedSourceVersion() { ... } public synchronized void init(ProcessingEnvironment processingEnv) { ... } public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv); }
  22. @auditty +AudreyTroutt javax.annotation.processing.Processor com.audreytroutt.WatchedMethodProcessor com.example.SomeOtherProcessor

  23. @auditty +AudreyTroutt WatchedMethodProcessor.jar Two files: • com/audreytroutt/WatchedMethodProcessor.class • META-INF/services/javax.annotation.processing.Proces sor

  24. @auditty +AudreyTroutt WatchedMethodAnnotations.jar • com/audreytroutt/WatchedMethod.class

  25. @auditty +AudreyTroutt Include in Project dependencies { compile files('libs/WatchedMethodAnnotations.jar') annotationProcessor

    files('libs/WatchedMethodProcessor.jar') ... }
  26. @auditty +AudreyTroutt • Can’t modify existing files Annotation Processors

  27. @auditty +AudreyTroutt Additional API class ExampleActivity extends Activity { @BindView(R.id.user)

    EditText username; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); //... } }
  28. @auditty +AudreyTroutt Additional API class ExampleActivity extends Activity { @BindView(R.id.user)

    EditText username; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); //... } }
  29. @auditty +AudreyTroutt Generated Code public class ExampleActivity$$ViewBinder<T extends com.samples.ExampleActivity> implements

    ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 21313618, “field ‘user’”); target.username = finder.castView(view, 21313618, “field ‘user’”); } @Override public void reset(T target) { target.username = null; } }
  30. @auditty +AudreyTroutt Generated Code public class ExampleActivity$$ViewBinder<T extends com.samples.ExampleActivity> implements

    ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 21313618, “field ‘user’”); target.username = finder.castView(view, 21313618, “field ‘user’”); } @Override public void reset(T target) { target.username = null; } }
  31. @auditty +AudreyTroutt Generated Code public class ExampleActivity$$ViewBinder<T extends com.samples.ExampleActivity> implements

    ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 21313618, “field ‘user’”); target.username = finder.castView(view, 21313618, “field ‘user’”); } @Override public void reset(T target) { target.username = null; } }
  32. @auditty +AudreyTroutt Get Started • Leverage existing libraries • Peek

    inside and see how they work
  33. @auditty +AudreyTroutt https://github.com/JakeWharton/butterknife class ExampleActivity extends Activity { @BindView(R.id.user) EditText

    username; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); //... } }
  34. @auditty +AudreyTroutt https://github.com/airbnb/DeepLinkDispatch @DeepLink("foo://example.com/deepLink/{id}") public class MainActivity extends Activity {

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) { Bundle parameters = intent.getExtras(); String idString = parameters.getString("id"); // Do something with idString } }
  35. @auditty +AudreyTroutt Some good blog posts: • http://hannesdorfmann.com/annotation-proc essing/annotationprocessing101 •

    https://medium.com/@lgvalle/how-butterknif e-actually-works-85be0afbc5ab
  36. @auditty +AudreyTroutt https://github.com/google/compile-testing Compilation compilation = javac() .withProcessors(new MyAnnotationProcessor()) .compile(JavaFileObjects.forResource("HelloWorld.java"));

    assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("GeneratedHelloWorld") .hasSourceEquivalentTo(JavaFileObjects.forResource( "GeneratedHelloWorld.java"));
  37. @auditty +AudreyTroutt https://github.com/square/javapoet MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class).addParameter(String[].class,

    "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!").build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(main).build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld).build(); javaFile.writeTo(System.out);