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

The power of Kotlin @Annotations

The power of Kotlin @Annotations

Avatar for Giorgio Antonioli

Giorgio Antonioli

October 05, 2018

Other Decks in Programming

Transcript

  1. • What an annotation is • How to apply it

    • How to create a custom annotation • How to create an annotation processor
  2. @SuppressWarnings /* java */ @SuppressLint /* android */ @DrawableRes /*

    android */ @Inject /* javax */ @SerializedName /* gson */ @GET /* retrofit */
  3. • Class • Annotation • Parameter • Property • Field

    • Local variable • Constructor • Getter • Setter • Expression • Type alias • Type • File
  4. @Ann class Foo class Foo @Ann constructor() @Ann fun foo(arg:

    Int): Int fun foo(@Ann arg: Int): Int @Ann typealias Bar = Foo
  5. class Bar { var foo: Int = 0 } ——————

    public final class Bar { private int foo; public final int getFoo() { return this.foo; } public final void setFoo(int var1) { this.foo = var1; } }
  6. class Bar { @field:FirstAnn @get:SecondAnn @set:ThirdAnn @setparam:FourthAnn var foo: Int

    = 0 } —————— public final class Bar { @FirstAnn private int foo; @SecondAnn public final int getFoo() { return this.foo; } @ThirdAnn public final void setFoo(@FourthAnn int var1) { this.foo = var1; } }
  7. class Bar { @field:[FirstAnn SecondAnn] var foo: Int = 0

    } —————— public final class Bar { @FirstAnn @SecondAnn private int foo; public final int getFoo() { return this.foo; } public final void setFoo(int var1) { this.foo = var1; } }
  8. • file • property • field • get • set

    • receiver • param • setparam • delegate
  9. • Java primitive types • Strings • Enums • Classes

    (KClass) • Annotations • Arrays of the types above
  10. AnnotationRetention.SOURCE -> not stored in binary output AnnotationRetention.BINARY -> stored

    in binary output, not visible for reflection at runtime AnnotationRetention.RUNTIME (default) -> stored in binary output and visible for reflection at runtime
  11. const val KITKAT = 19 const val LOLLIPOP = 21

    const val PIE = 28 // Same using @StringDef @IntDef(KITKAT, LOLLIPOP, PIE) @Retention(AnnotationRetention.SOURCE) annotation class AndroidVersion @AndroidVersion fun obtainAndroidVersion(): Int = when (Build.VERSION.SDK_INT) { Build.VERSION_CODES.KITKAT -> KITKAT Build.VERSION_CODES.LOLLIPOP -> LOLLIPOP Build.VERSION_CODES.P -> PIE else -> throw IllegalArgumentException("The version is unsupported") }
  12. • The processing runs at compile time • Uses KAPT

    (Kotlin Annotation Processing Tool) • Can not manipulate the existent code • Takes the source files as input and generates new source files • The processing runs in multiple rounds
  13. class PersonBuilder { private lateinit var name: String private var

    age: Int = 0 fun setName(name: String): PersonBuilder = apply { this.name = name } fun setAge(age: Int): PersonBuilder = apply { this.age = age } fun build(): Person = Person(name = name, age = age) }
  14. • builder-app • Person.kt (the model) • builder-annotations • BuilderSetter.kt

    (the annotation) • builder-processor • BuilderSetterProcessor.kt (the processor) • META-INF/services/javax.annotation.processing.Processor
  15. @AutoService(Processor::class) class BuilderSetterProcessor : AbstractProcessor() { private lateinit var messenger:

    Messager override fun init(env: ProcessingEnvironment) { super.init(env) messenger = env.messager } override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() override fun getSupportedAnnotationTypes(): Set<String> = setOf(BuilderSetter::class.java.canonicalName) override fun process( annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment ): Boolean { try { annotations.forEach { roundEnv.getElementsAnnotatedWith(it).forEach { elem -> // Process the annotated element elem.process() } } } catch (e: ProcessingException) { messenger.printMessage(Diagnostic.Kind.ERROR, e.msg, e.element) } return true } }