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

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

Jake Wharton

April 25, 2019
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

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

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

    / Bytecode generator Source transformer / Bytecode transformer
  3. 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) { // ... } } }
  4. 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) { // ... } } }
  5. 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) { // ... } } }
  6. 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) { // ... } } }
  7. 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) { // ... } } }
  8. 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) { // ... } } }
  9. Reflection • Only access to the shape of classes, methods,

    and fields • No access to content of methods
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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); } }); }
  15. 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); } }); }
  16. 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); } }); }
  17. 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); } }); }
  18. 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); } }); }
  19. 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); } }); }
  20. 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
  21. 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
  22. Reflection • GOOD: Debug tools and testing where performance is

    less of a concern • GOOD: When dominated by a slower operation like network I/O
  23. 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
  24. 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
  25. 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
  26. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

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

    / Bytecode generator Source transformer / Bytecode transformer
  28. {"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}]}
  29. • Use any format / schema / language as input

    • Be mindful of forward/backward compatibility with inputs Parsing
  30. • 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
  31. Parsing • GOOD: Re-use output formats from existing tools •

    GOOD: Behavior can be loaded from external resource like a server
  32. 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
  33. 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
  34. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

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

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

    name = 2; optional string email = 3; }
  37. 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() { .. } } }
  38. @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(); }
  39. @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(); }
  40. @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(); }
  41. @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(); }
  42. @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()... }
  43. • Only access to the shape of classes, methods, and

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

    fields • No access to content of methods Source Generator (Annotation Processor)
  45. • 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)
  46. • 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)
  47. • 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)
  48. • 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)
  49. • 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)
  50. • Generated source code can be public API and should

    feel like a library Source Generator (Other)
  51. • 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)
  52. • 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)
  53. • GOOD: Same performance and capability as hand-written code •

    GOOD: Relatively easy to create and change Source Generator
  54. • 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
  55. • 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
  56. • 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
  57. • 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
  58. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

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

    / Bytecode generator Source transformer / Bytecode transformer
  60. // 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>
  61. // 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; } }
  62. // 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
  63. // 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; }
  64. Bytecode Generator • GOOD: Same or better performance and capability

    as hand-written code • GOOD: Works extremely well with R8 and ProGuard
  65. 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
  66. 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
  67. 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
  68. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

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

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

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

    int compareTo(TimedRunnable that) { return Long.compare(execTime, that.execTime); } }
  72. 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
  73. 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); } }
  74. Source Transformer • Unlike annotation processing, you have access to

    method body content • Free to change, update, and delete any types, method, fields, and code
  75. 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
  76. Source Transformer • GOOD: Can be as simple or complex

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

    as needed • GOOD: Easy to get started and experiment with • MEH: Limited applicability
  78. 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
  79. Metaprogramming Runtime Compile-time Additive Mutating Reflection / Parser Source generator

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

    / Bytecode generator Source transformer / Bytecode transformer
  81. $ 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 }
  82. $ 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 }
  83. 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); } }; } }
  84. 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); } }; } }
  85. $ 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 }
  86. $ 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 }
  87. 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
  88. 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
  89. Bytecode Transformer • Free to change, update, and delete any

    types, method, fields, and code • Create constructs which cannot be represented in source
  90. 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
  91. Bytecode Transformer • GOOD: Unlimited power to create, modify, and

    delete anything as desired • OKAY: Build system integration not always easy
  92. 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
  93. 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
  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. 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
  97. // 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>
  98. 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
  99. 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
  100. 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( );
  101. // 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>
  102. // 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
  103. // 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
  104. // 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
  105. // 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
  106. // 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
  107. 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. 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
  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. 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
  125. 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
  126. // 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>
  127. // 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
  128. // 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
  129. // 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
  130. // 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
  131. // 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
  132. // 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
  133. // 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
  134. // 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
  135. // 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
  136. 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
  137. 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)
  138. CREATE TABLE user( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT

    NOT NULL, email TEXT NOT NULL ); SELECT email FROM user WHERE name LIKE 'Jake %'
  139. @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);
  140. 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
  141. @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
  142. @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
  143. @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
  144. @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
  145. 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 %'
  146. 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> }