Mechanisms of Metaprogramming (Chicago Roboto 2019)

Mechanisms of Metaprogramming (Chicago Roboto 2019)

Metaprogramming is all around us on Android in the form of reflection, annotation processing, Gradle plugins, and standalone tools. Some of the most popular and widespread libraries in use leverage one or more of these facilities. But none of these are the perfect tool for every problem. Each has advantages and disadvantages depending on how they’re used. And choosing the wrong one can actually harm the usability of your library.

This talk will compare and contrast the different approaches to metaprogramming, look at popular Android libraries and evaluate whether they made the right choice, and conclude with recommendations for those looking to use these techniques in the future.

Video: coming soon

E68309f117985270285ade8082f4877d?s=128

Jake Wharton

April 25, 2019
Tweet

Transcript

  1. Mechanisms of Metaprogramming @JakeWharton

  2. Metaprogramming

  3. Metaprogramming Runtime

  4. Metaprogramming Runtime Compile-time vs.

  5. Metaprogramming Runtime Compile-time vs. Additive

  6. Metaprogramming Runtime Compile-time vs. Additive Mutating vs.

  7. Metaprogramming Runtime Compile-time Additive Mutating

  8. Metaprogramming Runtime Compile-time Additive Mutating

  9. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser

  10. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator
  11. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  12. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  13. SomeLibrary.doSomething(User.class);

  14. SomeLibrary.doSomething(User.class); AnotherLibrary.doSomething(this);

  15. SomeLibrary.doSomething(User.class); AnotherLibrary.doSomething(this); class AnotherLibrary { static void doSomething(Object instance) {

    Class<?> cls = instance.getClass(); // ... } }
  16. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  17. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  18. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  19. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  20. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  21. for (Method method : moduleClass.getDeclaredMethods()) { Type returnType = method.getGenericReturnType();

    if ((method.getModifiers() & ABSTRACT) != 0) { if (method.getAnnotation(Binds.class) != null) { // ... } else if (method.getAnnotation(BindsOptionalOf.class) != null) { // ... } } else { if ((method.getModifiers() & STATIC) == 0 && instance == null) { // ... } if (method.getAnnotation(Provides.class) != null) { // ... } } }
  22. Reflection • Only access to the shape of classes, methods,

    and fields
  23. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods
  24. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written
  25. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written • Shape is immutable, no new methods, fields, or types
  26. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written • Shape is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types
  27. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written • Shape is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Relatively slow compared to using methods and fields normally
  28. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  29. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  30. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  31. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  32. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  33. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); return (T)

    Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return loadServiceMethod(method).invoke(args); } }); }
  34. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written • Shape is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Relatively slow compared to using methods and fields normally
  35. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods • Methods can be invoked, fields can be read/written • Shape is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Relatively slow compared to using methods and fields normally • Can create implementations of interfaces
  36. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern
  37. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern • GOOD: When dominated by a slower operation like network I/O
  38. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern • GOOD: When dominated by a slower operation like network I/O • OKAY: When on critical path and used infrequently
  39. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern • GOOD: When dominated by a slower operation like network I/O • OKAY: When on critical path and used infrequently • MEH: When used with R8 or ProGuard
  40. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern • GOOD: When dominated by a slower operation like network I/O • OKAY: When on critical path and used infrequently • MEH: When used with R8 or ProGuard • BAD: When on critical path and used heavily
  41. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  42. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  43. SomeLibrary.doSomething(R.raw.stuff);

  44. SomeLibrary.doSomething(R.raw.stuff); AnotherLibrary.doSomething("foo.txt");

  45. SomeLibrary.doSomething(R.raw.stuff); AnotherLibrary.doSomething("foo.txt"); Response response = // ... Library.doSomething(response.body().string());

  46. {"v":"4.8.0","fr":29.9700012207031,"ip":0,"op":61.0000024845809,"w":150,"h":150,"nm":"Name","ddd": 0,"assets":[],"fonts":{"list":[{"origin":0,"fPath":"","fClass":"","fFamily":"Comic Neue","fWeight":"","fStyle":"Regular","fName":"ComicNeue","ascent":69.6990966796875}]},"layers":[{"ddd": 0,"ind":1,"ty":5,"nm":"NAME","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x": 0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[10.5,15,0],"e": [10.5,147,0],"to":[0,22,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y": 0.167},"n":"0p833_0p833_0p167_0p167","t":15,"s":[10.5,147,0],"e":[10.5,15,0],"to":[0,0,0],"ti":[0,22,0]}, {"t":30.0000012219251}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y": [0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n": ["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":

    [100,100,100],"e":[196,196,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x": [0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n": ["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":15,"s": [196,196,100],"e":[100,100,100]},{"t":30.0000012219251}]}},"ao":0,"t":{"d":{"k":[{"s":{"s": 14,"f":"ComicNeue","t":"NAME","j":0,"tr":0,"lh":16.8,"ls":0,"fc":[0.92,0,0]},"t":0}]},"p":{},"m":{"g": 1,"a":{"a":0,"k":[0,0]}},"a":[{"s":{"t":0,"xe":{"a":0,"k":0},"ne":{"a":0,"k":0},"a":{"a":0,"k":100},"b": 1,"rn":0,"sh":1,"r":1},"a":{"fc":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y": [0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[1,0,0,1],"e":[0,1,0,1]},{"i":{"x":[0.833],"y": [0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":15,"s":[0,1,0,1],"e": [0,0,1,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n": ["0p833_0p833_0p167_0p167"],"t":30,"s":[0,0,1,1],"e":[0,1,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x": [0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":45,"s":[0,1,0,1],"e":[1,0,0,1]},{"t": 60.0000024438501}]},"t":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n": ["0p833_0p833_0p167_0p167"],"t":30,"s":[0],"e":[20]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y": [0.167]},"n":["0p833_0p833_0p167_0p167"],"t":45,"s":[20],"e":[0]},{"t":60.0000024438501}]}}}]},"ip": 0,"op":61.0000024845809,"st":0,"bm":0,"sr":1}]}
  47. None
  48. • Use any format / schema / language as input

    Parsing
  49. • Use any format / schema / language as input

    • Be mindful of forward/backward compatibility with inputs Parsing
  50. • Use any format / schema / language as input

    • Be mindful of forward/backward compatibility with inputs • Invoke existing framework APIs or ones from a library Parsing
  51. Parsing • GOOD: Re-use output formats from existing tools

  52. Parsing • GOOD: Re-use output formats from existing tools •

    GOOD: Behavior can be loaded from external resource like a server
  53. Parsing • GOOD: Re-use output formats from existing tools •

    GOOD: Behavior can be loaded from external resource like a server • OKAY: When on critical path and used infrequently
  54. Parsing • GOOD: Re-use output formats from existing tools •

    GOOD: Behavior can be loaded from external resource like a server • OKAY: When on critical path and used infrequently • BAD: When on critical path and used heavily
  55. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  56. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  57. message Person { required uint64 id = 1; required string

    name = 2; optional string email = 3; }
  58. message Person { required uint64 id = 1; required string

    name = 2; optional string email = 3; } class Person { long getId() { .. } String getName() { .. } String getEmail() { .. } class Builder { Builder setId(long id) { .. } Builder setName(String name) { .. } Builder setEmail(String email) { .. } Person build() { .. } } }
  59. @AutoValue abstract class Person { static Person from(long id, String

    name, String email) { return new AutoValue_Person(id, name, email); } abstract long id(); abstract String name(); abstract String email(); }
  60. @AutoValue abstract class Person { static Person from(long id, String

    name, String email) { return new AutoValue_Person(id, name, email); } abstract long id(); abstract String name(); abstract String email(); }
  61. @AutoValue abstract class Person { static Person from(long id, String

    name, String email) { return new AutoValue_Person(id, name, email); } abstract long id(); abstract String name(); abstract String email(); }
  62. @AutoValue abstract class Person { static Person from(long id, String

    name, String email) { return new AutoValue_Person(id, name, email); } abstract long id(); abstract String name(); abstract String email(); }
  63. @AutoValue abstract class Person { static Person from(long id, String

    name, String email) { return new AutoValue_Person(id, name, email); } abstract long id(); abstract String name(); abstract String email(); } final class AutoValue_Person extends Person { AutoValue_Person(long id, String name, String email) { .. } @Override abstract long id() { .. } @Override abstract String name() { .. } @Override abstract String email() { .. } // equals(), hashCode(), toString()... }
  64. • Only access to the shape of classes, methods, and

    fields Source Generator (Annotation Processor)
  65. • Only access to the shape of classes, methods, and

    fields • No access to content of methods Source Generator (Annotation Processor)
  66. • Only access to the shape of classes, methods, and

    fields • No access to content of methods • Shape of existing code is immutable, no new methods, fields, or types Source Generator (Annotation Processor)
  67. • Only access to the shape of classes, methods, and

    fields • No access to content of methods • Shape of existing code is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types Source Generator (Annotation Processor)
  68. • Only access to the shape of classes, methods, and

    fields • No access to content of methods • Shape of existing code is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Can generate any kind of type or a Java resource Source Generator (Annotation Processor)
  69. • Only access to the shape of classes, methods, and

    fields • No access to content of methods • Shape of existing code is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Can generate any kind of type or a Java resource • Avoid leaking generated code into API, treat as implementation detail Source Generator (Annotation Processor)
  70. • Only access to the shape of classes, methods, and

    fields • No access to content of methods • Shape of existing code is immutable, no new methods, fields, or types • Annotations can be read from method, field, parameters, and types • Can generate any kind of type or a Java resource • Avoid leaking generated code into API, treat as implementation detail • Use JavaPoet, KotlinPoet, auto-common, and/or compile-testing to build Source Generator (Annotation Processor)
  71. • Generated source code can be public API and should

    feel like a library Source Generator (Other)
  72. • Generated source code can be public API and should

    feel like a library • You can generate inputs to other tools like ProGuard rules or drawable XML Source Generator (Other)
  73. • Generated source code can be public API and should

    feel like a library • You can generate inputs to other tools like ProGuard rules or drawable XML • Use JavaPoet and KotlinPoet for source generation Source Generator (Other)
  74. • GOOD: Same performance and capability as hand-written code Source

    Generator
  75. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change Source Generator
  76. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change • GOOD: Works extremely well with R8 and ProGuard Source Generator
  77. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change • GOOD: Works extremely well with R8 and ProGuard • OKAY: Can contribute a lot of methods, fields, and allocations Source Generator
  78. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change • GOOD: Works extremely well with R8 and ProGuard • OKAY: Can contribute a lot of methods, fields, and allocations • MEH: Referencing generated code from annotation processors Source Generator
  79. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change • GOOD: Works extremely well with R8 and ProGuard • OKAY: Can contribute a lot of methods, fields, and allocations • MEH: Referencing generated code from annotation processors • MEH: Build performance impact requires care Source Generator
  80. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  81. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  82. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  83. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" /> <ImageButton

    android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout>
  84. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" /> <ImageButton

    android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> final class R { static final class id { static final int class_name = 0x7f0000001; static final int more_options = 0x7f000002; static final int package_name = 0x7f000003; } static final class layout { static final int search_item = 0x7f000004; } }
  85. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" /> <ImageButton

    android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> $ find build -name 'R.*' build/intermediates/r_class_jar/release/generateReleaseRFile/R.jar build/intermediates/r_class_jar/debug/generateDebugRFile/R.jar
  86. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" /> <ImageButton

    android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> $ find build -name 'R.*' build/intermediates/r_class_jar/release/generateReleaseRFile/R.jar build/intermediates/r_class_jar/debug/generateDebugRFile/R.jar $ javap -cp build/.../generateReleaseRFile/R.jar 'com.example.R$layout' public final class com.jakewharton.sdksearch.search.ui.R$layout { public static final int class_name; public static final int more_options; public static final int package_name; }
  87. Bytecode Generator • Same basic guidelines as source generation

  88. Bytecode Generator • Same basic guidelines as source generation •

    Use ASM or ByteBuddy, or some kind of IR
  89. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code
  90. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code • GOOD: Works extremely well with R8 and ProGuard
  91. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code • GOOD: Works extremely well with R8 and ProGuard • GOOD: Avoids the need to run Java and Kotlin compiler
  92. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code • GOOD: Works extremely well with R8 and ProGuard • GOOD: Avoids the need to run Java and Kotlin compiler • MEH: Somewhat hard to write and maintain
  93. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code • GOOD: Works extremely well with R8 and ProGuard • GOOD: Avoids the need to run Java and Kotlin compiler • MEH: Somewhat hard to write and maintain • BAD: No source code to step through while debugging
  94. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  95. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  96. class TimedRunnable implements Comparable<TimedRunnable> { final long execTime; @Override public

    int compareTo(TimedRunnable that) { return Long.compare(execTime, that.execTime); } }
  97. class TimedRunnable implements Comparable<TimedRunnable> { final long execTime; @Override public

    int compareTo(TimedRunnable that) { return Long.compare(execTime, that.execTime); } }
  98. class TimedRunnable implements Comparable<TimedRunnable> { final long execTime; @Override public

    int compareTo(TimedRunnable that) { return Long.compare(execTime, that.execTime); } } java/lang/Long.compare --> io/reactivex/internal/java/lang/Long.compare
  99. class TimedRunnable implements Comparable<TimedRunnable> { final long execTime; @Override public

    int compareTo(TimedRunnable that) { return Long.compare(execTime, that.execTime); } } java/lang/Long.compare --> io/reactivex/internal/java/lang/Long.compare class TimedRunnable implements Comparable<TimedRunnable> { final long execTime; @Override public int compareTo(TimedRunnable that) { return io.reactivex.internal.java.lang.Long.compare( execTime, that.execTime); } }
  100. Source Transformer • Unlike annotation processing, you have access to

    method body content
  101. Source Transformer • Unlike annotation processing, you have access to

    method body content • Free to change, update, and delete any types, method, fields, and code
  102. Source Transformer • Unlike annotation processing, you have access to

    method body content • Free to change, update, and delete any types, method, fields, and code • Use Spoon for complicated transformations, regex for simple ones
  103. Source Transformer • GOOD: Can be as simple or complex

    as needed
  104. Source Transformer • GOOD: Can be as simple or complex

    as needed • GOOD: Easy to get started and experiment with
  105. Source Transformer • GOOD: Can be as simple or complex

    as needed • GOOD: Easy to get started and experiment with • MEH: Limited applicability
  106. Source Transformer • GOOD: Can be as simple or complex

    as needed • GOOD: Easy to get started and experiment with • MEH: Limited applicability • BAD: Transformations are brittle and prone to breakage
  107. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  108. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  109. $ javap -c build/classes/main/io/reactivex/internal/schedulers/TrampolineScheduler\$TimedRunnable.class Compiled from "TrampolineScheduler.java" final class io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable

    implements java.lang.Comparable<io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable> { final java.lang.Runnable run; final long execTime; public int compareTo(io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable); Code: 0: aload_0 1: getfield #5 // Field execTime:J 4: aload_1 5: getfield #5 // Field execTime:J 8: invokestatic #7 // Method java/lang/Long.compare:(JJ)I 11: ireturn }
  110. $ javap -c build/classes/main/io/reactivex/internal/schedulers/TrampolineScheduler\$TimedRunnable.class Compiled from "TrampolineScheduler.java" final class io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable

    implements java.lang.Comparable<io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable> { final java.lang.Runnable run; final long execTime; public int compareTo(io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable); Code: 0: aload_0 1: getfield #5 // Field execTime:J 4: aload_1 5: getfield #5 // Field execTime:J 8: invokestatic #7 // Method java/lang/Long.compare:(JJ)I 11: ireturn }
  111. final class BackportingMethodCallRemapper extends ClassVisitor { BackportingMethodCallRemapper(ClassVisitor cv) { super(ASM5,

    cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (mv == null) { return null; } return new MethodVisitor(ASM5, mv) { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if ("java/lang/Long".equals(owner) && "compare".equals(name)) { owner = "io/reactivex/internal/java/lang/Long"; } super.visitMethodInsn(opcode, owner, name, desc, itf); } }; } }
  112. final class BackportingMethodCallRemapper extends ClassVisitor { BackportingMethodCallRemapper(ClassVisitor cv) { super(ASM5,

    cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (mv == null) { return null; } return new MethodVisitor(ASM5, mv) { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if ("java/lang/Long".equals(owner) && "compare".equals(name)) { owner = "io/reactivex/internal/java/lang/Long"; } super.visitMethodInsn(opcode, owner, name, desc, itf); } }; } }
  113. $ javap -c build/classes/main/io/reactivex/internal/schedulers/TrampolineScheduler\$TimedRunnable.class Compiled from "TrampolineScheduler.java" final class io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable

    implements java.lang.Comparable<io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable> { final java.lang.Runnable run; final long execTime; public int compareTo(io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable); Code: 0: aload_0 1: getfield #5 // Field execTime:J 4: aload_1 5: getfield #5 // Field execTime:J 8: invokestatic #7 // Method io/reactivex/internal/java/lang/Long.compare:(JJ)I 11: ireturn }
  114. $ javap -c build/classes/main/io/reactivex/internal/schedulers/TrampolineScheduler\$TimedRunnable.class Compiled from "TrampolineScheduler.java" final class io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable

    implements java.lang.Comparable<io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable> { final java.lang.Runnable run; final long execTime; public int compareTo(io.reactivex.internal.schedulers.TrampolineScheduler$TimedRunnable); Code: 0: aload_0 1: getfield #5 // Field execTime:J 4: aload_1 5: getfield #5 // Field execTime:J 8: invokestatic #7 // Method io/reactivex/internal/java/lang/Long.compare:(JJ)I 11: ireturn }
  115. public static void main(String... args) { sayHi(s -> System.out.println(s)); }

  116. public static void main(String... args) { sayHi(s -> System.out.println(s)); }

    BootstrapMethods: 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:( Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/ MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #28 (Ljava/lang/String;)V #29 invokestatic Java8.lambda$main$0:(Ljava/lang/String;)V #28 (Ljava/lang/String;)V
  117. public static void main(String... args) { sayHi(s -> System.out.println(s)); }

    [0002bc] Java8.main:([Ljava/lang/String;)V 0000: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; 0003: new-instance v0, L-$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM; 0004: invoke-direct {v0, v1}, L-$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM;.<init>: (Ljava/io/PrintStream;)V 0008: invoke-static {v0}, LJava8;.sayHi:(LJava8$Logger;)V
  118. Bytecode Transformer • Free to change, update, and delete any

    types, method, fields, and code
  119. Bytecode Transformer • Free to change, update, and delete any

    types, method, fields, and code • Create constructs which cannot be represented in source
  120. Bytecode Transformer • Free to change, update, and delete any

    types, method, fields, and code • Create constructs which cannot be represented in source • Use ASM or ByteBuddy, or some kind of IR
  121. Bytecode Transformer • GOOD: Unlimited power to create, modify, and

    delete anything as desired
  122. Bytecode Transformer • GOOD: Unlimited power to create, modify, and

    delete anything as desired • OKAY: Build system integration not always easy
  123. Bytecode Transformer • GOOD: Unlimited power to create, modify, and

    delete anything as desired • OKAY: Build system integration not always easy • OKAY: Can affect debugging and stacktraces in non-obvious ways
  124. Bytecode Transformer • GOOD: Unlimited power to create, modify, and

    delete anything as desired • OKAY: Build system integration not always easy • OKAY: Can affect debugging and stacktraces in non-obvious ways • MEH: Requires a lot of machinery and knowledge of bytecode
  125. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  126. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

    / Bytecode generator Source transformer / Bytecode transformer
  127. Network Requests

  128. GET /users HTTP/1.1 [{id:1,name:"Jake"}]

  129. [{id:1,name:"Jake"}] GET /users HTTP/1.1

  130. [{id:1,name:"Jake"}] GET /users HTTP/1.1

  131. interface GitHubService { suspend fun users(): List<User> }X data class

    User( val id: Long, val login: String )Y
  132. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X data

    class User( val id: Long, val login: String )Y
  133. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X @JsonClass

    data class User( val id: Long, val login: String )Y
  134. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X @JsonClass

    data class User( val id: Long, val login: String )Y
  135. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X @JsonClass

    data class User( val id: Long, val login: String )Y
  136. GET /users HTTP/1.1 [{id:1,name:"Jake"}]

  137. GET /users HTTP/1.1 [{id:1,name:"Jake"}]

  138. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X @JsonClass

    data class User( val id: Long, val login: String )Y
  139. interface GitHubService { @GET("/users") suspend fun users(): List<User> }X @JsonClass

    data class User( val id: Long, val login: String )Y
  140. GET /users HTTP/1.1 [{id:1,name:"Jake"}] interface GitHubService { @GET("/users") suspend fun

    users(): List<User> }X @JsonClass data class User( val id: Long, val login: String )Y
  141. interface GitHubService {A @GET("/users") suspend fun users(): List<User> }X

  142. class GitHubService(val client: OkHttpClient) {A suspend fun users(): List<User> {

    // ... }B }X i n t e r f a c e @GET("/users")
  143. Android Layouts

  144. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  145. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); }B }A
  146. private private private packageNameView = findViewById( ); classNameView = findViewById(

    ); moreOptionsView = findViewById( ); class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); }B }A
  147. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) }B }A e x t e n d s @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; protected void ButterKnife.bind(this); void setText( ); setText( );
  148. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  149. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 1. Layout file name
  150. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 1. Layout file name 2. Root view type
  151. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 1. Layout file name 2. Root view type 3. Type of each view with ID
  152. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout
  153. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/package_name"

    android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/more_options" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/class_name" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@+id/package_name" app:layout_constraintEnd_toEndOf="@+id/package_name" app:layout_constraintTop_toBottomOf="@+id/package_name" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/more_options" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_more_vert_black_24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  154. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  155. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  156. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration 2. Root view type
  157. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration 2. Root view type 3. Type of each view with ID
  158. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 5. Whether an ID is present in
 every layout configuration 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout
  159. class SearchItemActivity extends Activity { private TextView packageNameView; private TextView

    classNameView; private ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); packageNameView = findViewById(R.id.package_name); classNameView = findViewById(R.id.class_name); moreOptionsView = findViewById(R.id.more_options); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  160. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  161. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  162. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  163. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  164. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  165. class SearchItemActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.class_name) TextView

    classNameView; @BindView(R.id.more_options) ImageButton moreOptionsView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_item); ButterKnife.bind(this); }B void later() { packageName.setText("com.example"); className.setText("ExampleClass"); moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  166. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  167. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  168. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  169. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  170. class SearchItemActivity : Activity { @Override fun onCreate(savedInstanceState: Bundle) {

    super.onCreate(savedInstanceState) setContentView(R.layout.search_item) }B fun later() { packageName.text = "com.example" className.text = "ExampleClass" moreOptions.setImageResource(R.drawable.view_source) } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration 6. Kotlin-only
  171. class SearchItemActivity extends Activity { private SearchItemBinding layout; @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); layout = setContentView(this, R.layout.search_item); }B void later() { layout.packageName.setText("com.example"); layout.className.setText("ExampleClass"); layout.moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration 2. Root view type
  172. class SearchItemActivity extends Activity { private SearchItemBinding layout; @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); layout = setContentView(this, R.layout.search_item); }B void later() { layout.packageName.setText("com.example"); layout.className.setText("ExampleClass"); layout.moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  173. // search_item.xml <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" /> <ImageButton

    android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout>
  174. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
  175. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin
  176. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A
  177. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A
  178. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A
  179. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin Java compiler annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A
  180. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin Java compiler annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A class SearchItemBindingImpl extends SearchItemBinding { // ... }A
  181. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin Java compiler annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A class SearchItemBindingImpl extends SearchItemBinding { // ... }A
  182. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin Java compiler annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A class SearchItemBindingImpl extends SearchItemBinding { // ... }A
  183. // search_item.xml <layout> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/package_name" /> <TextView android:id="@+id/class_name" />

    <ImageButton android:id="@+id/more_options" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> Android Gradle plugin Java compiler annotationProcessor 'androidx.databinding:databinding-compiler:3.4.0' @Binding class SearchItemBinding { public final TextView packageName; public final TextView className; public final ImageButton moreOptions; }A class SearchItemBindingImpl extends SearchItemBinding { // ... }A
  184. class SearchItemActivity extends Activity { private SearchItemBinding layout; @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); layout = SearchItemBinding.inflate(this); setContentView(layout); }B void later() { layout.packageName.setText("com.example"); layout.className.setText("ExampleClass"); layout.moreOptions.setImageResource(R.drawable.view_source); } }A 1. Layout file name 2. Root view type 3. Type of each view with ID 4. Which IDs are contained
 inside each layout 5. Whether an ID is present in
 every layout configuration
  185. class SearchItemActivity extends Activity { private SearchItemBinding layout; @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); layout = SearchItemBinding.inflate(this); setContentView(layout); }B void later() { layout.packageName.setText("com.example"); layout.className.setText("ExampleClass"); layout.moreOptions.setImageResource(R.drawable.view_source); } }A (HINT)
  186. Database Queries

  187. CREATE TABLE user( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT

    NOT NULL, email TEXT NOT NULL ); SELECT email FROM user WHERE name LIKE 'Jake %'
  188. @Table("user") class User { @PrimaryKey @AutoIncrement long id; @NotNull String

    name; @NotNull String email; }A Database db = Database.create(context, User.class, ..); List<String> jakes = db.from(User.class) .where("name", like("Jake %")) .select("email") .toList(String.class);
  189. Table @A Increment Database db = Database.create(context, User.class, ..); List<String>

    jakes = db.from(User.class) .where("name", like("Jake %")) .select("email") .toList(String.class); @Entity(tableName = "user") class User { @PrimaryKey(autoGenerate = true) long id; @NotNull String name; @NotNull String email; }A @Dao interface UserDao { @Query("SELECT email FROM user WHERE name LIKE 'JAKE %'") List<String> jakes(); }H
  190. @Entity(tableName = "user") class User { @PrimaryKey(autoGenerate = true) long

    id; @NotNull String name; @NotNull String email; }A @Dao interface UserDao { @Query("SELECT email FROM user WHERE name LIKE 'JAKE %'") List<String> jakes(); }H
  191. @Entity(tableName = "user") class User { @PrimaryKey(autoGenerate = true) long

    id; @NotNull String name; @NotNull String email; }A @Dao interface UserDao { @Query("SELECT name, email FROM user WHERE name LIKE 'JAKE %'") List<???> jakes(); }H S t r i n g User
  192. @Entity(tableName = "user") class User { @PrimaryKey(autoGenerate = true) long

    id; @NotNullA String name; @NotNullB String email; }A @Dao interface UserDao { @Query("SELECT * FROM user WHERE name LIKE 'JAKE %'") List<User> jakes(); }H
  193. @Entity(tableName = "user") class User { @PrimaryKey(autoGenerate = true) long

    id; @NotNullAString name; @NotNullBString email; @Nullable LocalDate birthday; @NonNull LocalDate created; boolean is_admin; @Nullable String home_address; @Nullable String phone_number; }A @Dao interface UserDao { @Query("SELECT * FROM user WHERE name LIKE 'JAKE %'") List<User> jakes(); }H
  194. CREATE TABLE user( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT

    NOT NULL, email TEXT NOT NULL ); SELECT name, email FROM user WHERE name LIKE 'Jake %'
  195. CREATE TABLE user( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT

    NOT NULL, email TEXT NOT NULL ); SELECT name, email FROM user WHERE name LIKE 'Jake %' data class User( val id: Long, val name: String, val email: String ) data class JakeResult( val name: String, val email: String ) interface UserQueries { fun jakes(): Query<JakeResult> }
  196. Mechanisms of Metaprogramming @JakeWharton