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

Generating Kotlin extension functions for Annot...

Generating Kotlin extension functions for Annotation Processing

Presented:
* 27th of October 2018 - DevFest Wrocław 2018, Poland
* 5th of April 2019 - Droidcon Italy 2019
* 11th of April 2019 - Toast Wrocław #23

Avatar for Bartosz Lipinski

Bartosz Lipinski

October 27, 2018

Other Decks in Programming

Transcript

  1. @blipinsk Compiler (CAN BE) HEAVY should never be included in

    codebase looks for uses of Annotations CAN GENERATE CODE!
  2. @blipinsk compile time only new code unknown until generated compile

    time only new code unknown until generated WHAT WILL BE GENERATED? WHEN WILL IT BE GENERATED?
  3. @blipinsk “I don’t care” approach Dagger 2 dependency injection framework

    generates a lot of code one entry point to the generated code (class that implements your component) Dagger<Component>
  4. @blipinsk “I don’t care” approach // … import com.bartoszlipinski.example.DaggerAppComponent class

    ExampleApplication : Application() { private val component: AppComponent by lazy { DaggerAppComponent .builder() .appModule(AppModule(this)) .build() } override fun onCreate() { super.onCreate() component.inject(this) } }
  5. @blipinsk “I don’t care” approach // … import com.bartoszlipinski.example.DaggerAppComponent class

    ExampleApplication : Application() { private val component: AppComponent by lazy { DaggerAppComponent .builder() .appModule(AppModule(this)) .build() } override fun onCreate() { super.onCreate() component.inject(this) } } Dagger will give you this class in COMPILE-TIME (if everything goes correctly)
  6. @blipinsk “I don’t care” approach • easy to implement (for

    the developer of the library) • not straighforward • strongly depends on documentation • puts responsibility on the user of the library
  7. @blipinsk “ReDection bridge” approach BuJerKnife reduces Android boilerplate code (for

    views, resources, etc.) generates _ViewBinding classes available through ButterKnife.bind(…)
  8. @blipinsk “ReDection bridge” approach // Generated code from Butter Knife.

    Do not modify! package com.barteklipinski.sample; // imports … public final class MainActivity_ViewBinding implements Unbinder { // … class contents } Re2ection bridge
  9. @blipinsk “ReDection bridge” approach // Generated code from Butter Knife.

    Do not modify! package com.barteklipinski.sample; // imports … public final class MainActivity_ViewBinding implements Unbinder { // … class contents } Re2ection bridge
  10. @blipinsk “ReDection bridge” approach // Generated code from Butter Knife.

    Do not modify! package com.barteklipinski.sample; // imports … public final class MainActivity_ViewBinding implements Unbinder { // … class contents } Re2ection bridge
  11. @blipinsk “ReDection bridge” approach // Generated code from Butter Knife.

    Do not modify! package com.barteklipinski.sample; // imports … public final class MainActivity_ViewBinding implements Unbinder { // … class contents } Re2ection bridge
  12. @blipinsk • @nd generated classes • create instances of them

    • return those instances in a form of the Unbinder inteBace “ReDection bridge” approach val unbinder : Unbinder = ButterKnife.bind(target) Re2ection bridge
  13. @blipinsk • straighforward • puts responsibility on the developer of

    the library (not user) • depends on Java ReEection (some peBormance overhead) “ReDection bridge” approach
  14. @blipinsk “Extension bridge” approach Compiler Library Extension bridge “End Cap”

    compile time Generated Code Extension bridge “Core” knows how compiler works PRETENDS TO BE REAL
  15. @blipinsk “Extension bridge” approach Compiler Library Extension bridge “End Cap”

    compile time Generated Code runtime Extension bridge “Core” NOT using* Java Reflection
  16. @blipinsk Why Kotlin extension functions? Annotation processors cannot modify exisiting

    code. They can only generate new code. fun String.toUpperCamelCase(): String { // convert… } add* behaviour to existing classes (without modifying them) "DROIDcon ItAly".toUpperCamelCase() // == “DroidconItaly” Receiver Type
  17. @blipinsk “Extension bridge” proof of concept libraries Dagger 2 !

    BuRerKnife ! h9ps://github.com/blipinsk/dagger2-ktx h9ps://github.com/blipinsk/bu9erknife-ktx
  18. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  19. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  20. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  21. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  22. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  23. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library NON-SPECIFIC
  24. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  25. @blipinsk fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife

    “Extension bridge” proof of concept fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView() return MainActivity_ViewBinding(target, sourceView) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  26. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  27. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library MUCH MORE SPECIFIC
  28. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  29. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  30. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  31. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  32. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  33. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library ORIGINALLY ACHIEVED WITH JAVA REFLECTION
  34. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  35. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  36. @blipinsk object ButterKnifeKtx @UiThread fun ButterKnifeKtx.bind(target: Activity): Unbinder {…} @UiThread

    fun ButterKnifeKtx.bind(target: View): Unbinder {…} @UiThread fun ButterKnifeKtx.bind(target: Dialog): Unbinder {…} @UiThread fun ButterKnifeKtx.bind(target: Any, source: Activity): Unbinder {…} @UiThread fun ButterKnifeKtx.bind(target: Any, source: View): Unbinder {…} @UiThread fun ButterKnifeKtx.bind(target: Any, source: Dialog): Unbinder {…} BuRerKnife “Extension bridge” proof of concept Extension bridge “End Cap” Artificial object declaration (singleton)
  37. @blipinsk Dagger 2 “Extension bridge” proof of concept object Dagger

    fun <T : Any> Dagger.create(clazz: KClass<T>): T { throw IllegalStateException() } fun Dagger.create(clazz: KClass<AppComponent>): AppComponent { return DaggerAppComponent.create() } Extension bridge “End Cap” shipped as library Extension bridge “Core” generated DaggerAppComponent.create() Dagger.create(AppComponent::class)
  38. @blipinsk Dagger 2 “Extension bridge” proof of concept object Dagger

    fun <T : Any> Dagger.create(clazz: KClass<T>): T { throw IllegalStateException() } fun Dagger.create(clazz: KClass<AppComponent>): AppComponent { return DaggerAppComponent.create() } Extension bridge “End Cap” shipped as library Extension bridge “Core” generated DaggerAppComponent.create() Dagger.create(AppComponent::class)
  39. @blipinsk Dagger 2 “Extension bridge” proof of concept object Dagger

    fun <T : Any> Dagger.create(clazz: KClass<T>): T { throw IllegalStateException() } fun Dagger.create(clazz: KClass<AppComponent>): AppComponent { return DaggerAppComponent.create() } DaggerAppComponent.create() Dagger.create(AppComponent::class) Extension bridge “End Cap” shipped as library Extension bridge “Core” generated
  40. @blipinsk “Extension bridge”- how? FunSpec.builder("bind") .returns(ClassName("butterknife", "Unbinder")) .receiver(ClassName("com.bartoszlipinski.butterknifektx", "ButterKnifeKtx")) .addParameter("target",

    targetClassName) .addStatement("return %T(%L, %L)", initializedClassName, "target", "target") .build() dependencies { implementation ‘com.squareup:kotlinpoet:{version}’ }
  41. @blipinsk • straighforward • puts responsibility on the developer of

    the library (not user) • does NOT depend on Java ReEection* “Extension bridge” approach
  42. @blipinsk hybrid “Extension bridge” approach Compiler Library Extension bridge “End

    Cap” compile time Generated Code Extension bridge “Core”
  43. @blipinsk hybrid “Extension bridge” approach Compiler Library Extension bridge “End

    Cap” compile time Generated Code runtime Extension bridge “Core”
  44. @blipinsk hybrid “Extension bridge” approach Compiler Library Extension bridge “End

    Cap” compile time Generated Code runtime Extension bridge “Core” USING Java Reflection
  45. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library BuRerKnife hybrid “Extension bridge”
  46. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { throw IllegalStateException() } BuRerKnife hybrid “Extension bridge” Extension bridge “End Cap” Extension bridge “Core” generated shipped as library
  47. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { return ButterKnife.bind(target) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library BuRerKnife hybrid “Extension bridge”
  48. @blipinsk fun ButterKnifeKtx.bind(target: MainActivity): Unbinder { val sourceView = target.getWindow().getDecorView()

    return MainActivity_ViewBinding(target, sourceView) } fun ButterKnifeKtx.bind(target: Activity): Unbinder { return ButterKnife.bind(target) } Extension bridge “End Cap” Extension bridge “Core” generated shipped as library BuRerKnife hybrid “Extension bridge”