$30 off During Our Annual Pro Sale. View Details »

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
    Android Annotation
    Processors
    Audrey Troutt

    View Slide

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

    }
    Have you used them? Yes, you have

    View Slide

  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

    View Slide

  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

    View Slide

  5. @auditty
    +AudreyTroutt
    Who creates Annotations?
    ● Library developers, mostly

    View Slide

  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

    View Slide

  7. @auditty
    +AudreyTroutt
    #import
    // 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

    View Slide

  8. @auditty
    +AudreyTroutt
    Annotations
    ● Define metadata about code

    View Slide

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

    View Slide

  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

    View Slide

  11. @auditty
    +AudreyTroutt

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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 getSupportedAnnotationTypes() {
    return Collections.singleton("com.audreytroutt.WatchedMethod");
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }
    ...

    View Slide

  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 getSupportedAnnotationTypes() {
    return Collections.singleton("com.audreytroutt.WatchedMethod");
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }
    ...

    View Slide

  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 getSupportedAnnotationTypes() {
    return Collections.singleton("com.audreytroutt.WatchedMethod");
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }
    ...

    View Slide

  21. @auditty
    +AudreyTroutt
    Annotation Processor
    package javax.annotation.processing;
    public abstract class AbstractProcessor implements Processor {
    public Set getSupportedAnnotationTypes() { ... }
    public SourceVersion getSupportedSourceVersion() { ... }
    public synchronized void init(ProcessingEnvironment processingEnv) { ... }
    public abstract boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv);
    }

    View Slide

  22. @auditty
    +AudreyTroutt
    javax.annotation.processing.Processor
    com.audreytroutt.WatchedMethodProcessor
    com.example.SomeOtherProcessor

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. @auditty
    +AudreyTroutt
    ● Can’t modify existing files
    Annotation Processors

    View Slide

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

    View Slide

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

    View Slide

  29. @auditty
    +AudreyTroutt
    Generated Code
    public class ExampleActivity$$ViewBinder
    implements ViewBinder {
    @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;
    }
    }

    View Slide

  30. @auditty
    +AudreyTroutt
    Generated Code
    public class ExampleActivity$$ViewBinder
    implements ViewBinder {
    @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;
    }
    }

    View Slide

  31. @auditty
    +AudreyTroutt
    Generated Code
    public class ExampleActivity$$ViewBinder
    implements ViewBinder {
    @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;
    }
    }

    View Slide

  32. @auditty
    +AudreyTroutt
    Get Started
    ● Leverage existing libraries
    ● Peek inside and see how they work

    View Slide

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

    View Slide

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

    View Slide

  35. @auditty
    +AudreyTroutt
    Some good blog posts:
    ● http://hannesdorfmann.com/annotation-proc
    essing/annotationprocessing101
    ● https://medium.com/@lgvalle/how-butterknif
    e-actually-works-85be0afbc5ab

    View Slide

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

    View Slide

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

    View Slide