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

@AnnotationProcessors("ByExample") (Droidcon NY...

@AnnotationProcessors("ByExample") (Droidcon NYC 2015)

Annotations and Annotation Processors are all the rage in Android Development these days. There are annotations to help you:

- Avoid the boilerplate of Parcelable or ContentProviders
- Generate bindings for your views
- Implementing deeplinking
- Even find bugs in your code!

This talk will showcase some existing annotations (like the support-annotations library) and annotation processors (such as butterknife, deeplinkdispatch, autoparcel). Afterwards, I’ll show you how to write your own.

Michael Evans

August 28, 2015
Tweet

More Decks by Michael Evans

Other Decks in Programming

Transcript

  1. public class MainActivity extends AppCompatActivity {
 
 @Override protected void

    onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 
 @Override public boolean onCreateOptionsMenu(Menu menu) {
 getMenuInflater().inflate(R.menu.menu_main, menu);
 return true;
 }
 
 }
  2. public class MainActivity extends AppCompatActivity {
 
 @Override protected void

    onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 
 @Override public boolean onCreateOptionsMenu(Menu menu) {
 getMenuInflater().inflate(R.menu.menu_main, menu);
 return true;
 }
 
 }
  3. • Java 1.5 - JSR 175 • “A metadata facility

    that would allow classes, interfaces, fields, and methods to be marked as having particular attributes.” • @Override, @Deprecared, @SuppressWarnings (and friends) Annotations
  4. • @Override • Causes a compile error if the method

    is not found in one of the parent classes or implemented interfaces. • @Deprecated • Causes a compile warning if the method is used. • @SuppressWarnings • Instructs the compiler to suppress the compile time warnings
  5. • Nullness • @Nullable / @NonNull • Resource Type annotations

    • @StringRes, @LayoutRes, @IdRes, etc Support Annotations
  6. • Nullness • @Nullable / @NonNull • Resource Type annotations

    • @StringRes, @LayoutRes, @IdRes, etc • @RequiresPermission Support Annotations
  7. • Nullness • @Nullable / @NonNull • Resource Type annotations

    • @StringRes, @LayoutRes, @IdRes, etc • @RequiresPermission • @Keep Support Annotations
  8. Butterknife @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 


    EditText title = (EditText) findViewById(R.id.title);
 EditText subtitle = (EditText) findViewById(R.id.subtitle);
 Button submit = (Button) findViewById(R.id.submit);
 submit.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 //...
 }
 });
 } https://github.com/JakeWharton/butterknife
  9. Butterknife @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 


    EditText title = (EditText) findViewById(R.id.title);
 EditText subtitle = (EditText) findViewById(R.id.subtitle);
 Button submit = (Button) findViewById(R.id.submit);
 submit.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 //...
 }
 });
 } https://github.com/JakeWharton/butterknife
  10. Butterknife @Bind(R.id.title) EditText title;
 @Bind(R.id.subtitle) EditText subtitle;
 
 @Override protected

    void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 ButterKnife.bind(this);
 }
 
 @OnClick(R.id.submit)
 public void submit() {
 //...
 } https://github.com/JakeWharton/butterknife
  11. Dagger • Dependency injector for Android and Java public class

    Example {
 @Inject Foo foo;
 @Inject Bar bar;
 
 public void baz() {
 foo.doSomething();
 bar.doSomethingElse();
 }
 } https://github.com/square/dagger
  12. Dagger • Dependency injector for Android and Java • That’s

    another show https://github.com/square/dagger public class Example {
 @Inject Foo foo;
 @Inject Bar bar;
 
 public void baz() {
 foo.doSomething();
 bar.doSomethingElse();
 }
 }
  13. DeeplinkDispatch public class MainActivity extends AppCompatActivity {
 
 @Override protected

    void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 } https://github.com/airbnb/DeepLinkDispatch
  14. @DeepLink("foo://example.com/deepLink/{id}")
 public class MainActivity extends AppCompatActivity {
 
 @Override protected

    void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 if (getIntent().getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {
 Bundle parameters = getIntent().getExtras();
 
 String id = parameters.getString("id");
 
 // Do something with the id
 }
 } DeeplinkDispatch https://github.com/airbnb/DeepLinkDispatch
  15. GAlette @SendEvent(category = "HelloWorld", action = "sayHello", label="%1$s")
 String sayHello

    (String name) {
 return String.format("Hello, %s.", name);
 } https://github.com/uPhyca/GAlette
  16. @Override
 public void onActivityResult(int requestCode, int resultCode, Intent data) {


    // retrieve the error code, if available
 int errorCode = -1;
 if (data != null) {
 errorCode = data.getIntExtra(WalletConstants.EXTRA_ERROR_CODE, -1);
 }
 switch (requestCode) {
 case REQUEST_CODE_MASKED_WALLET:
 switch (resultCode) {
 case Activity.RESULT_OK:
 MaskedWallet maskedWallet =
 data.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET);
 launchConfirmationPage(maskedWallet);
 break;
 case Activity.RESULT_CANCELED:
 break;
 default:
 handleError(errorCode);
 break;
 }
 break;
  17. case REQUEST_CODE_RESOLVE_ERR:
 if (resultCode == Activity.RESULT_OK) {
 mGoogleApiClient.connect();
 } else

    {
 handleUnrecoverableGoogleWalletError(errorCode);
 }
 break;
 case REQUEST_CODE_RESOLVE_LOAD_FULL_WALLET:
 switch (resultCode) {
 case Activity.RESULT_OK:
 if (data.hasExtra(WalletConstants.EXTRA_FULL_WALLET)) {
 FullWallet fullWallet =
 data.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET);
 fetchTransactionStatus(fullWallet);
 }
 }
  18. case REQUEST_CODE_CHANGE_MASKED_WALLET:
 if (resultCode == Activity.RESULT_OK &&
 data.hasExtra(WalletConstants.EXTRA_MASKED_WALLET)) {
 mMaskedWallet

    = data.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET);
 ((FullWalletConfirmationButtonFragment) getResultTargetFragment())
 .updateMaskedWallet(mMaskedWallet);
 }
 break;
 case WalletConstants.RESULT_ERROR:
 int errorCode = data.getIntExtra(WalletConstants.EXTRA_ERROR_CODE, 0);
 handleError(errorCode);
 break;
 default:
 super.onActivityResult(requestCode, resultCode, data);
 break;
  19. public abstract class AbstractProcessor implements Processor {
 
 Set<String> getSupportedAnnotationTypes();


    
 SourceVersion getSupportedSourceVersion();
 
 synchronized void init(ProcessingEnvironment processingEnv);
 
 boolean process(Set<? extends TypeElement> annotations,
 RoundEnvironment roundEnv);
 
 }

  20. @Override
 public Set<String> getSupportedAnnotationTypes() {
 return Collections.singleton( OnActivityResult.class.getCanonicalName());
 }
 


    @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 }
  21. @Override
 public synchronized void init(ProcessingEnvironment env) {
 super.init(processingEnv);
 filer =

    env.getFiler();
 messager = env.getMessager();
 elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils();
 }
  22. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {


    
 for (Element element : env.getElementsAnnotatedWith(OnActivityResult.class)) {
 messager.printMessage(Diagnostic.Kind.WARNING, element.getSimpleName());
 } return false; }
  23. public class Example { //TypeElement
 
 private int foo; //VariableElement


    
 public Example() { //ExecuteableElement
 
 }
 
 public void setFoo(int i) {
 foo = i;
 }
 }
  24. @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    for (Element element : roundEnv.getElementsAnnotatedWith(OnActivityResult.class)) {
 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
 BindingClass bindingClass = getOrCreateAftermath(targetClassMap, enclosingElement, erasedTargetNames);
 bindingClass.createAndAddResultBinding(element);
 ...
 } for (BindingClass bindingClass : targetClassMap.values()) {
 try {
 bindingClass.writeToFiler(filer);
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
 }
 }
  25. "package com.example;",
 "",
 "import android.content.Intent;",
 "import java.lang.Override;",
 "import org.michaelevans.aftermath.Aftermath;",
 "",


    "public class MainActivity$$Aftermath<T extends com.example.MainActivity>"
 +" implements Aftermath.IOnActivityForResult<T> {",
 " @Override",
 " public void onActivityResult(final T target, final int requestCode,"
 +" final int resultCode, final Intent data) {",
 " if(requestCode == 1) {",
 " target.onContactPicked(resultCode, data);",
 " }",
 " }",
 "}"
  26. https://github.com/square/javapoet A Java API for generating .java source files. 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);
  27. final class BindingClass { private final Map<Integer, OnActivityResultBinding> activityResultBindings; void

    createAndAddResultBinding(Element element) {
 OnActivityResultBinding binding = new OnActivityResultBinding(element);
 activityResultBindings.put(binding.requestCode, binding);
 } void writeToFiler(Filer filer) throws IOException {
 ClassName targetClassName = ClassName.get(classPackage, targetClass);
 TypeSpec.Builder aftermath = TypeSpec.classBuilder(className)
 .addModifiers(Modifier.PUBLIC)
 .addTypeVariable(TypeVariableName.get("T", targetClassName))
 .addMethod(generateOnActivityResultMethod());
 
 ClassName callback = ClassName.get("org.michaelevans.aftermath", "IOnActivityForResult");
 aftermath.addSuperinterface(ParameterizedTypeName.get(callback,
 TypeVariableName.get("T")));
 
 JavaFile javaFile = JavaFile.builder(classPackage, aftermath.build()).build();
 javaFile.writeTo(filer);
 } ...
  28. private MethodSpec generateOnActivityResultMethod() {
 MethodSpec.Builder builder = MethodSpec.methodBuilder("onActivityResult")
 .addAnnotation(Override.class)
 .addModifiers(Modifier.PUBLIC)


    .returns(void.class)
 .addParameter(TypeVariableName.get("T"), "target", Modifier.FINAL)
 .addParameter(int.class, "requestCode", Modifier.FINAL)
 .addParameter(int.class, "resultCode", Modifier.FINAL)
 .addParameter(ClassName.get("android.content", "Intent"), "data", Modifier.FINAL);
 
 if (!activityResultBindings.isEmpty()) {
 for (OnActivityResultBinding binding : activityResultBindings.values()) {
 builder.beginControlFlow("if (requestCode == $L)", binding.requestCode);
 builder.addStatement("target.$L(resultCode, data)", binding.name);
 builder.endControlFlow();
 }
 }
 
 return builder.build();
 }
  29. @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {


    Aftermath.onActivityResult(this, requestCode, resultCode, data);
 super.onActivityResult(requestCode, resultCode, data);
 }
  30. public static void onActivityResult(Object target, int requestCode, int resultCode, Intent

    data) {
 Class<?> targetClass = target.getClass();
 if (debug) {
 Log.d(TAG, "Looking up aftermath for " + targetClass.getName());
 }
 IOnActivityForResult<Object> aftermath = findActivityForResultForClass(targetClass);
 if (aftermath != NO_OP) {
 aftermath.onActivityResult(target, requestCode, resultCode, data);
 }
 }
  31. package org.michaelevans.aftermath.sample;
 
 import android.content.Intent;
 import java.lang.Override;
 import org.michaelevans.aftermath.Aftermath;
 


    public class MainActivity$$Aftermath<T extends MainActivity> implements Aftermath.IOnActivityForResult<T> {
 @Override
 public void onActivityResult(final T target, final int requestCode, final int resultCode, final Intent data) {
 if (requestCode == 1) {
 target.onContactPicked(resultCode, data);
 }
 if (requestCode == 2) {
 target.onOtherRequest(resultCode, data);
 }
 }
 }

  32. • Can only generate new files • Can’t manipulate already

    existing files • Use android-apt Tips