Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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.

Audrey Troutt

May 04, 2017
Tweet

More Decks by Audrey Troutt

Other Decks in Technology

Transcript

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

    super.onResume(); … } Have you used them? Yes, you have
  2. @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
  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 #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
  5. @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
  6. @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
  7. @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
  8. @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; } }
  9. @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(); } ...
  10. @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(); } ...
  11. @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(); } ...
  12. @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); }
  13. @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); //... } }
  14. @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); //... } }
  15. @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; } }
  16. @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; } }
  17. @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; } }
  18. @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); //... } }
  19. @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 } }
  20. @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"));
  21. @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);