Code Generation in Kotlin with Annotation and KotlinPoet

Code Generation in Kotlin with Annotation and KotlinPoet

In this session, I talked about how we can implement an annotation processor into our Kotlin project together with KotlinPoet to generate Kotlin source files. I then presented some strategies to test and validate the generated code. Discussed the benefits and limitations of this approach and when the appropriate time to use it is. Finally, I provided some sample implementation of annotation processing which we can use to increase our productivity.

6338c8fa4e2e6325094fe30b1e9f9443?s=128

Malvin Sutanto

August 24, 2019
Tweet

Transcript

  1. 1.

    ©2019 Wantedly, Inc. Code Generation in Kotlin with Annotation and

    KotlinPoet Kotlin Fest 2019 Aug 24, 2019 - Malvin Sutanto Photo by Dave Michuda on Unsplash
  2. 4.

    ©2019 Wantedly, Inc. Treat other programs as their data To

    add or change behavior and functionalities • Runtime: Reflection and parser • Compile time: Generator Metaprogramming https://jakewharton.com/mechanisms-of-metaprogramming/
  3. 5.

    ©2019 Wantedly, Inc. Read files and properties during runtime Read

    from Java or Kotlin classes • Read variables • Invoke functions Interpret files, like XML or JSON Metaprogramming Reflection and parser
  4. 7.

    ©2019 Wantedly, Inc. Generate files based on input Annotation Can

    output source code, byte code, or any type of files Can be compile safe Better performance during runtime Dagger 2 for dependency injection Metaprogramming Generator
  5. 8.

    ©2019 Wantedly, Inc. Dagger 2 Example Metaprogramming @Inject lateinit var

    repository: Repository !!... DaggerComponent.inject(this)
  6. 10.

    ©2019 Wantedly, Inc. API added in Java 6 Reads representation

    of your code • Not the code itself • Code has not been compiled yet • Special APIs to read code Annotation Processor Annotation
  7. 11.

    ©2019 Wantedly, Inc. Annotation Processor Convert data class to a

    map @AutoMap data class Person( val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } @AutoMap data class Address( val streetName: String, val city: String ) /** * Converts [Address] to [Map]. !*/ fun Address.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "streetName" to streetName, "city" to city ) return map }
  8. 12.

    ©2019 Wantedly, Inc. Annotation Processor Annotation module !// :annotation/build.gradle apply

    plugin: 'kotlin' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” } Java/Kotlin module
  9. 16.

    ©2019 Wantedly, Inc. Annotation Processor Processor module !// :processor/build.gradle apply

    plugin: 'kotlin' apply plugin: ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” }
  10. 17.

    ©2019 Wantedly, Inc. Annotation Processor Processor module !// :processor/build.gradle apply

    plugin: 'kotlin' apply plugin: ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” } Java/Kotlin module
  11. 20.

    ©2019 Wantedly, Inc. !// :processor/build.gradle apply plugin: 'kotlin' apply plugin:

    ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” } AutoMapProcessor Kotlin metadata
  12. 21.

    ©2019 Wantedly, Inc. AutoMapProcessor Kotlin metadata !// :processor/build.gradle apply plugin:

    'kotlin' apply plugin: ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” }
  13. 22.

    ©2019 Wantedly, Inc. AutoMapProcessor Kotlin metadata @Metadata( mv = {1,

    1, 15}, bv = {1, 0, 3}, k = 1, d1 = {"\u00002\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\t\n\u0000\n\u0002\u0010\u00 0e\n\u0000\n\u0002\u0010 \n\u0002\u0018\u0002\n\u0002\b\f\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u000 2\b\u0086\b\u0018\u00002\u00020\u0001B% \u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\b\u0010\u0004\u001a\u0004\u0018\u00010\u0005\u001 2\f\u0010\u0006\u001a\b\u0012\u0004\u0012\u00020\b0\u0007¢ \u0006\u0002\u0010\tJ\t\u0010\u0010\u001a\u0002!!...”}, d2 = {"Lcom/malvinstn/myannotationprocessing/Person;", "", "id", "", "name", "", "address", "", "Lcom/malvinstn/myannotationprocessing/Address;", "(JLjava/lang/String;Ljava/util/List;)V", "getAddress", "()Ljava/util/List;", "getId", "()J", "getName", "()Ljava/lang/String;", "component1", "component2", "component3", "copy", "equals", "", "other", "hashCode", "", "toString", "app_debug"} ) public final class Person {
  14. 24.

    ©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(),

    KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set<String> = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }
  15. 25.

    ©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(),

    KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set<String> = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }
  16. 26.

    ©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(),

    KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set<String> = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }
  17. 27.

    ©2019 Wantedly, Inc. !// :processor/build.gradle apply plugin: 'kotlin' apply plugin:

    ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” } AutoMapProcessor Auto service
  18. 28.

    ©2019 Wantedly, Inc. AutoMapProcessor Auto service !// :processor/build.gradle apply plugin:

    'kotlin' apply plugin: ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” }
  19. 29.

    ©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(),

    KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set<String> = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }
  20. 30.

    ©2019 Wantedly, Inc. AutoMapProcessor Processor class annotated with AutoService @AutoService(Processor!::class)

    class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set<String> = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }
  21. 31.

    ©2019 Wantedly, Inc. AutoMapProcessor Kotlin class syntax class KotlinClass {

    private var count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  22. 32.

    ©2019 Wantedly, Inc. AutoMapProcessor Type class KotlinClass { private var

    count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  23. 33.

    ©2019 Wantedly, Inc. AutoMapProcessor Members class KotlinClass { private var

    count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  24. 34.

    ©2019 Wantedly, Inc. AutoMapProcessor Parameter class KotlinClass { private var

    count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  25. 35.

    ©2019 Wantedly, Inc. AutoMapProcessor Top level member class KotlinClass {

    private var count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  26. 36.

    ©2019 Wantedly, Inc. AutoMapProcessor Return type class KotlinClass { private

    var count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  27. 37.

    ©2019 Wantedly, Inc. AutoMapProcessor Visibility modifier class KotlinClass { private

    var count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  28. 38.

    ©2019 Wantedly, Inc. AutoMapProcessor Kotlin class syntax class KotlinClass {

    private var count: Int = 0 fun incrementCount() { count += 1 } fun incrementCountBy(value: Int) { count += value } } fun KotlinClass.printCount(): String = "Count is: $count"
  29. 40.

    ©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override

    fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { val elements = roundEnv.getElementsAnnotatedWith(AutoMap!::class.java) !// Loop through all annotated element for (element in elements) { !// Check if it is a public data class. !// Read the declared variables of each element. !// Check if a declared variable’s Type is also annotated with @AutoMap. !// Generate code with KotlinPoet. } return true }
  30. 41.

    ©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override

    fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { val elements = roundEnv.getElementsAnnotatedWith(AutoMap!::class.java) !// Loop through all annotated element for (element in elements) { !// Check if it is a public data class. !// Read the declared variables of each element. !// Check if a declared variable’s Type is also annotated with @AutoMap. !// Generate code with KotlinPoet. } return true }
  31. 42.

    ©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override

    fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { val elements = roundEnv.getElementsAnnotatedWith(AutoMap!::class.java) !// Loop through all annotated element for (element in elements) { !// Check if it is a public data class. !// Read the declared variables of each element. !// Check if a declared variable’s Type is also annotated with @AutoMap. !// Generate code with KotlinPoet. } return true } for (element in elements)
  32. 43.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. val metadata: KotlinClassMetadata? = element.kotlinMetadata as? KotlinClassMetadata if (element !is TypeElement !|| metadata !== null) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a class", element) continue } val classProto: ProtoBuf.Class = metadata.data.classProto !!... } for (element in elements)
  33. 44.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. val metadata: KotlinClassMetadata? = element.kotlinMetadata as? KotlinClassMetadata if (element !is TypeElement !|| metadata !== null) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a class", element) continue } val classProto: ProtoBuf.Class = metadata.data.classProto !!... }
  34. 45.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. val metadata: KotlinClassMetadata? = element.kotlinMetadata as? KotlinClassMetadata if (element !is TypeElement !|| metadata !== null) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a class", element) continue } val classProto: ProtoBuf.Class = metadata.data.classProto !!... }
  35. 46.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. val metadata: KotlinClassMetadata? = element.kotlinMetadata as? KotlinClassMetadata if (element !is TypeElement !|| metadata !== null) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a class", element) continue } val classProto: ProtoBuf.Class = metadata.data.classProto !!... }
  36. 47.

    ©2019 Wantedly, Inc. Processing Kotlin code ProtoBuf.Class val classProto: ProtoBuf.Class

    = metadata.data.classProto classProto.constructorList classProto.supertypeList classProto.typeParameterList classProto.functionList classProto.modality !!...
  37. 48.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. val metadata: KotlinClassMetadata? = element.kotlinMetadata as? KotlinClassMetadata if (element !is TypeElement !|| metadata !== null) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a class", element) continue } val classProto: ProtoBuf.Class = metadata.data.classProto !!... } for (element in elements) { val classProto: ProtoBuf.Class = metadata.data.classProto
  38. 49.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. !!... val classProto: ProtoBuf.Class = metadata.data.classProto if (!classProto.isDataClass !|| !// Is not a data class. classProto.visibility !!= ProtoBuf.Visibility.PUBLIC !|| !// Is not public. classProto.isInnerClass !// Is an inner class. ) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a public data class", element) continue } !// Read the declared variables of each element. } val classProto: ProtoBuf.Class = metadata.data.classProto for (element in elements) {
  39. 50.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. !!... val classProto: ProtoBuf.Class = metadata.data.classProto if (!classProto.isDataClass !|| !// Is not a data class. classProto.visibility !!= ProtoBuf.Visibility.PUBLIC !|| !// Is not public. classProto.isInnerClass !// Is an inner class. ) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a public data class", element) continue } !// Read the declared variables of each element. }
  40. 51.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. !!... val classProto: ProtoBuf.Class = metadata.data.classProto if (!classProto.isDataClass !|| !// Is not a data class. classProto.visibility !!= ProtoBuf.Visibility.PUBLIC !|| !// Is not public. classProto.isInnerClass !// Is an inner class. ) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a public data class", element) continue } !// Read the declared variables of each element. }
  41. 52.

    ©2019 Wantedly, Inc. Processing Kotlin code Validating element for (element

    in elements) { !// Check if it is a valid element. !!... val classProto: ProtoBuf.Class = metadata.data.classProto if (!classProto.isDataClass !|| !// Is not a data class. classProto.visibility !!= ProtoBuf.Visibility.PUBLIC !|| !// Is not public. classProto.isInnerClass !// Is an inner class. ) { messager.printMessage(Diagnostic.Kind.ERROR, "Not a public data class", element) continue } !// Read the declared variables of each element. } !// Read the declared variables of each element. for (element in elements) {
  42. 53.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) } !// Read the declared variables of each element. for (element in elements) {
  43. 54.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) }
  44. 55.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) }
  45. 56.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) }
  46. 57.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) }
  47. 58.

    ©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for

    (element in elements) { !!... !// Read the declared variables of each element. val variables: List<Pair<String, TypeElement?!>> = element.enclosedElements .filterIsInstance<VariableElement>() .map { variable !-> !// Check if a declared variable’s Type is also annotated with @AutoMap. val varType: TypeElement? = typeUtils.asElement(variable.asType()) as? TypeElement val hasAnnotation: Boolean = varType!?.getAnnotation(AutoMap!::class.java) !!= null variable.simpleName.toString() to varType!?.takeIf { hasAnnotation } } generateCode(element, variables) }
  48. 60.

    ©2019 Wantedly, Inc. !// :processor/build.gradle apply plugin: 'kotlin' apply plugin:

    ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” } Generate Kotlin Code KotlinPoet
  49. 61.

    ©2019 Wantedly, Inc. Generate Kotlin Code KotlinPoet !// :processor/build.gradle apply

    plugin: 'kotlin' apply plugin: ‘kotlin-kapt' dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib:1.3.41” implementation project(':annotation') !// Kotlin metadata implementation “me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0” !// Configuration generator for service providers implementation “com.google.auto.service:auto-service:1.0-rc6“ kapt "com.google.auto.service:auto-service:1.0-rc6" !// Code generation library for kotlin implementation “com.squareup:kotlinpoet:1.2.0” }
  50. 62.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  51. 63.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  52. 64.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  53. 65.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  54. 66.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  55. 67.

    ©2019 Wantedly, Inc. Generate Kotlin Code @AutoMap data class Person(

    val id: Long, val name: String?, val address: Address ) /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  56. 68.

    ©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person]

    to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  57. 69.

    ©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person]

    to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  58. 70.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder()
  59. 71.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  60. 72.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  61. 73.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", “Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  62. 74.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  63. 75.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  64. 76.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val mapOfFunction: MemberName = MemberName("kotlin.collections", “mapOf") val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)
  65. 77.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val mapOfFunction: MemberName = MemberName("kotlin.collections", “mapOf") val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction) .indent() codeBlockBuilder.unindent() .addStatement(")") .addStatement("return map")
  66. 78.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val mapOfFunction: MemberName = MemberName("kotlin.collections", “mapOf") val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction) .indent() codeBlockBuilder.unindent() .addStatement(")") .addStatement("return map")
  67. 79.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map<String, Any?> .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val mapOfFunction: MemberName = MemberName("kotlin.collections", “mapOf") val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction) .indent() !// Loop through all variables variables.forEachIndexed { !!... } codeBlockBuilder.unindent() .addStatement(")") .addStatement("return map") variables.forEachIndexed
  68. 80.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... !// Loop through all variables variables.forEachIndexed { index: Int, (name: String, varType: TypeElement?) !-> val comma = if (index < variables.size - 1) "," else "" if (varType !!= null) { !// Another class that is annotated with @AutoMap, use toMap instead. val toMap = MemberName(varType.asClassName().packageName, "toMap") codeBlockBuilder.addStatement("%S to %L.%M()%L", name, name, toMap, comma) } else { codeBlockBuilder.addStatement("%S to %L%L", name, name, comma) } } variables.forEachIndexed
  69. 81.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... !// Loop through all variables variables.forEachIndexed { index: Int, (name: String, varType: TypeElement?) !-> val comma = if (index < variables.size - 1) "," else "" if (varType !!= null) { !// Another class that is annotated with @AutoMap, use toMap instead. val toMap = MemberName(varType.asClassName().packageName, "toMap") codeBlockBuilder.addStatement("%S to %L.%M()%L", name, name, toMap, comma) } else { codeBlockBuilder.addStatement("%S to %L%L", name, name, comma) } }
  70. 82.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... !// Loop through all variables variables.forEachIndexed { index: Int, (name: String, varType: TypeElement?) !-> val comma = if (index < variables.size - 1) "," else "" if (varType !!= null) { !// Another class that is annotated with @AutoMap, use toMap instead. val toMap = MemberName(varType.asClassName().packageName, "toMap") codeBlockBuilder.addStatement("%S to %L.%M()%L", name, name, toMap, comma) } else { codeBlockBuilder.addStatement("%S to %L%L", name, name, comma) } }
  71. 83.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... !// Loop through all variables variables.forEachIndexed { index: Int, (name: String, varType: TypeElement?) !-> val comma = if (index < variables.size - 1) "," else "" if (varType !!= null) { !// Another class that is annotated with @AutoMap, use toMap instead. val toMap = MemberName(varType.asClassName().packageName, "toMap") codeBlockBuilder.addStatement("%S to %L.%M()%L", name, name, toMap, comma) } else { codeBlockBuilder.addStatement("%S to %L%L", name, name, comma) !// !-> “id” to id, } }
  72. 84.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... !// Loop through all variables variables.forEachIndexed { index: Int, (name: String, varType: TypeElement?) !-> val comma = if (index < variables.size - 1) "," else "" if (varType !!= null) { !// Another class that is annotated with @AutoMap, use toMap instead. val toMap = MemberName(varType.asClassName().packageName, "toMap") codeBlockBuilder.addStatement("%S to %L.%M()%L", name, name, toMap, comma) } else { codeBlockBuilder.addStatement("%S to %L%L", name, name, comma) !// !-> “id” to id, } }
  73. 85.

    ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... val funSpec = FunSpec.builder("toMap") .addCode(codeBlockBuilder.build()) .build()
  74. 86.

    ©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person]

    to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  75. 87.

    ©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person]

    to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample
  76. 88.

    ©2019 Wantedly, Inc. Generate Kotlin Code Receiver type val returnType:

    ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = !!... val className: ClassName = element.asClassName() val funSpec = FunSpec.builder("toMap") .receiver(className) .addCode(codeBlockBuilder.build()) .build()
  77. 89.

    ©2019 Wantedly, Inc. Generate Kotlin Code Receiver type val returnType:

    ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = !!... val className: ClassName = element.asClassName() val funSpec = FunSpec.builder("toMap") .receiver(className) .addCode(codeBlockBuilder.build()) .build() fun Person.toMap(): Map<String, Any?> { !!... }
  78. 90.

    ©2019 Wantedly, Inc. Generate Kotlin Code Parameterized return type val

    returnType: ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = !!... val className: ClassName = !!... val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addCode(codeBlockBuilder.build()) .build()
  79. 91.

    ©2019 Wantedly, Inc. Generate Kotlin Code Parameterized return type val

    returnType: ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = !!... val className: ClassName = !!... val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addCode(codeBlockBuilder.build()) .build() fun Person.toMap(): Map<String, Any?> { !!... }
  80. 92.

    ©2019 Wantedly, Inc. Generate Kotlin Code FunSpec, other options FunSpec.builder(!!...)

    .addKDoc(!!...) .addAnnotation(MyAnnotation!::class) .addParameter(ParameterSpec) .addModifiers(!!...) .beginControlFlow() .endControlFlow() .nextControlFlow(!!...) !!...
  81. 93.

    ©2019 Wantedly, Inc. Generate Kotlin Code Parameterized return type val

    returnType: ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = !!... val className: ClassName = !!... val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addCode(codeBlockBuilder.build()) .build() val funSpec = FunSpec.builder("toMap")
  82. 94.

    ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec val className: ClassName

    = !!... val funSpec = FunSpec.builder("toMap") !!... FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) !!... val funSpec = FunSpec.builder("toMap")
  83. 95.

    ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec val className: ClassName

    = !!... val funSpec = FunSpec.builder("toMap") !!... FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) !!...
  84. 96.

    ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec val className: ClassName

    = !!... val funSpec = FunSpec.builder("toMap") !!... FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) !!...
  85. 97.

    ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec, other options FileSpec.builder(!!...)

    .addType(TypeSpec) .addAnnotation(MyAnnotation!::class) .addProperty(PropertySpec) .addComment("This is a generated file. Do not edit”) !!...
  86. 98.

    ©2019 Wantedly, Inc. Generate Kotlin Code Documentation val mapClass =

    ClassName("kotlin.collections", "Map") val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addKdoc("Converts [%T] to [%T].", className, mapClass) .addCode(codeBlockBuilder.build()) .build() FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) .addComment("This is a generated file. Do not edit") .build() .writeTo(outputDir)
  87. 99.

    ©2019 Wantedly, Inc. Generate Kotlin Code Documentation val mapClass =

    ClassName("kotlin.collections", "Map") val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addKdoc("Converts [%T] to [%T].", className, mapClass) .addCode(codeBlockBuilder.build()) .build() FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) .addComment("This is a generated file. Do not edit") .build() .writeTo(outputDir)
  88. 100.

    ©2019 Wantedly, Inc. Generate Kotlin Code Documentation val mapClass =

    ClassName("kotlin.collections", "Map") val funSpec = FunSpec.builder("toMap") .receiver(className) .returns(returnType) .addKdoc("Converts [%T] to [%T].", className, mapClass) .addCode(codeBlockBuilder.build()) .build() FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) .addComment("This is a generated file. Do not edit") .build() .writeTo(outputDir)
  89. 101.

    ©2019 Wantedly, Inc. Generate Kotlin Code End result package com.malvinstn.myannotationprocessing

    import com.malvinstn.annotation.AutoMap @AutoMap data class Person( val id: Long, val name: String?, val address: Address ) !// This is a generated file. Do not edit package com.malvinstn.myannotationprocessing import kotlin.Any import kotlin.String import kotlin.collections.Map import kotlin.collections.mapOf /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map<String, Any?> { val map: Map<String, Any?> = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map }
  90. 104.

    ©2019 Wantedly, Inc. Testing Generated Code Test module class PersonTest

    { @Test fun `test when name is null map"name" is null`() { val person = Person( id = 1, name = null, address = Address( streetName = "streetName", city = "city" ) ) val map = person.toMap() assertNull(map["name"]) } }
  91. 105.

    ©2019 Wantedly, Inc. Testing Generated Code Test module class PersonTest

    { @Test fun `test when name is null map"name" is null`() { val person = Person( id = 1, name = null, address = Address( streetName = "streetName", city = "city" ) ) val map = person.toMap() assertNull(map["name"]) } }
  92. 106.

    ©2019 Wantedly, Inc. Good for testing happy paths But unable

    to test and validate error cases Page Title Page Subtitle
  93. 107.

    ©2019 Wantedly, Inc. Kotlin Compile Testing • https://github.com/tschuchortdev/kotlin-compile-testing Kompile Testing

    • https://github.com/permissions-dispatcher/kompile-testing Testing Generated Code Testing libraries
  94. 108.

    ©2019 Wantedly, Inc. 1. Add dependency as testImplementation • To

    :processor module 2. Write source code in String format • Or load from a File 3. Use compile API from the library 4. Validate compiler’s result • And validate generated classes Testing Generated Code Testing libraries
  95. 109.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing @Test

    fun `test AutoMap, when applied to data class, then generate toMap function`() { val result = KotlinCompilation().apply { sources = listOf( SourceFile.new( “Person.kt", """ import com.malvinstn.annotation.AutoMap @AutoMap data class Person(val id: Long, val name: String?) """ ) ) annotationProcessors = listOf(AutoMapProcessor()) !!... }.compile() !!... }
  96. 110.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing @Test

    fun `test AutoMap, when applied to data class, then generate toMap function`() { val result = KotlinCompilation().apply { sources = listOf( SourceFile.new( “Person.kt", """ import com.malvinstn.annotation.AutoMap @AutoMap data class Person(val id: Long, val name: String?) """ ) ) annotationProcessors = listOf(AutoMapProcessor()) !!... }.compile() !!... }
  97. 111.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing @Test

    fun `test AutoMap, when applied to data class, then generate toMap function`() { val result = !!...{ assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) !// Assert generated classes val kClazz = result.classLoader.loadClass("PersonKt") val paramClazz = result.classLoader.loadClass("Person") kClazz.getDeclaredMethod("toMap", paramClazz) }
  98. 112.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing @Test

    fun `test AutoMap, when applied to data class, then generate toMap function`() { val result = !!...{ assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) !// Assert generated classes val kClazz = result.classLoader.loadClass("PersonKt") val paramClazz = result.classLoader.loadClass("Person") kClazz.getDeclaredMethod("toMap", paramClazz) }
  99. 113.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing @Test

    fun `test AutoMap, when applied to data class, then generate toMap function`() { val result = !!...{ assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) !// Assert generated classes val kClazz = result.classLoader.loadClass("PersonKt") val paramClazz = result.classLoader.loadClass("Person") kClazz.getDeclaredMethod("toMap", paramClazz) }
  100. 114.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing public

    final class PersonKt { @NotNull public static final Map toMap(@NotNull Person $this$toMap) { Intrinsics.checkParameterIsNotNull($this$toMap, "$this$toMap"); Map map = MapsKt.mapOf(new Pair[]{TuplesKt.to("id", $this$toMap.getId()), TuplesKt.to("name", $this$toMap.getName()), TuplesKt.to("address", AddressKt.toMap($this$toMap.getAddress()))}); return map; } }
  101. 115.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing -

    testing error case @Test fun `test AutoMap, when applied to non data class, then exit with compilation error`() { val result = KotlinCompilation().apply { sources = listOf( SourceFile.new( "Person.kt", """ import com.malvinstn.annotation.AutoMap @AutoMap class Person(val id: Long, val name: String?) """ ) ) !!... }.compile() assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) }
  102. 116.

    ©2019 Wantedly, Inc. Testing Generated Code Kotlin compile testing -

    testing error case @Test fun `test AutoMap, when applied to non data class, then exit with compilation error`() { val result = KotlinCompilation().apply { sources = listOf( SourceFile.new( "Person.kt", """ import com.malvinstn.annotation.AutoMap @AutoMap class Person(val id: Long, val name: String?) """ ) ) !!... }.compile() assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) }
  103. 118.

    ©2019 Wantedly, Inc. Increase the overall build time • All

    steps need to be performed during compile time • Implement incremental kapt for better performance • Supported since Kotlin 1.3.30 • https://docs.gradle.org/5.0/userguide/ java_plugin.html#sec:incremental_annotation_processing • https://blog.jetbrains.com/kotlin/2019/04/kotlin-1-3-30-released/ Things to Consider Build time overhead
  104. 119.

    ©2019 Wantedly, Inc. Increased size of your app • Unable

    to modify existing files or classes • Only create additional classes and functions Things to Consider Code footprint
  105. 120.

    ©2019 Wantedly, Inc. Generated code are not accessible until kapt

    is run • “Unresolved references” error. • Might shadow the root cause of a compilation error Things to Consider Error handling and code visibility
  106. 122.

    ©2019 Wantedly, Inc. 1. Generate code based on members of

    an element 2. Focus on API over readability 3. Deal with repeated codes • Can be automated • Reduce boilerplate codes 4. Time investment Should We Use Annotation Processor?
  107. 125.

    ©2019 Wantedly, Inc. Sample Annotation Processor Variable List for Java

    and Kotlin Objects For Api Consumption @ApiEntity data class User( val id: Long, val name: String, val wanted_score: Int, val profile: Profile ) @ApiEntity data class Profile( val introduction: String ) val fields: List<String> = listOf( "id", "name", “wanted_score”, “profile.introduction”, )
  108. 126.

    ©2019 Wantedly, Inc. Sample Annotation Processor Sealed Class to Bundle

    For Analytics Log @AnalyticsEvent sealed class MyEvent { data class ShareImage(val imageName: String, val fullString: String) : MyEvent() object ButtonTapped : MyEvent() } /** * Converts [MyEvent] to event name and params and logs it using [EventTracker.logEvent]. * * This is a generated function. Do not edit. !*/ fun EventTracker.logEvent(event: MyEvent) { val name: String val params: Bundle when (event) { is MyEvent.ShareImage !-> { name = "share_image" params = bundleOf( "image_name" to event.imageName, "full_string" to event.fullString ) } is MyEvent.ButtonTapped !-> { name = "button_tapped" params = Bundle() } } logEvent(name, params) } medium.com/@malvinsutanto
  109. 127.

    ©2019 Wantedly, Inc. 1. Kotlin Metadata: https://github.com/Takhion/kotlin-metadata 2. KotlinPoet: https://square.github.io/kotlinpoet/

    3. Auto Service: https://github.com/google/auto/tree/master/service 4. Kotlin compile testing: https://github.com/tschuchortdev/kotlin-compile-testing 5. Kompile Testing: https://github.com/permissions-dispatcher/kompile-testing 6. Kotlin Metadata documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-metadata/index.html 7. Kotlinx Metadata: https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm 8. Better Analytics in Android with Annotation Processing and KotlinPoet: https://link.medium.com/Bh8wRWOY6Y 9. Mechanisms of Metaprogramming: https://jakewharton.com/mechanisms-of-metaprogramming/ 10.KotlinFest 2018 - Annotation Processing in a Kotlin World by Zac Sweers: https://youtu.be/a2RoLFzrFG0 11.Moshi: https://github.com/square/moshi Thank You! References and links Twitter/Medium: @malvinsutanto