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. ©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. ©2019 Wantedly, Inc. Introduction Malvin Sutanto Software Engineer @Wantedly Android,

    Kotlin. Twitter/ Medium: @malvinsutanto
  3. ©2019 Wantedly, Inc. Metaprogramming

  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/
  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
  6. ©2019 Wantedly, Inc. Considered a bad practice and difficult to

    maintain Page Title Page Subtitle
  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
  8. ©2019 Wantedly, Inc. Dagger 2 Example Metaprogramming @Inject lateinit var

    repository: Repository !!... DaggerComponent.inject(this)
  9. ©2019 Wantedly, Inc. Annotation Processor

  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
  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 }
  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
  13. ©2019 Wantedly, Inc. @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class AutoMap Annotation Processor

    Annotation class
  14. ©2019 Wantedly, Inc. @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class AutoMap Annotation Processor

    Annotation class
  15. ©2019 Wantedly, Inc. @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class AutoMap Annotation Processor

    Annotation class
  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” }
  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
  18. ©2019 Wantedly, Inc. Annotation Processor Dependency graph BQQMJDBUJPO BOOPUBUJPO QSPDFTTPS

    Implementation kapt implementation
  19. ©2019 Wantedly, Inc. AutoMapProcessor

  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
  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” }
  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 {
  23. ©2019 Wantedly, Inc. Kotlinx metadata • From JetBrains • https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm

    AutoMapProcessor Kotlinx metadata
  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") } }
  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") } }
  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") } }
  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
  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” }
  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") } }
  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") } }
  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"
  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"
  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"
  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"
  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"
  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"
  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"
  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"
  39. ©2019 Wantedly, Inc. Processing Kotlin code

  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 }
  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 }
  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)
  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)
  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 !!... }
  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 !!... }
  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 !!... }
  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 !!...
  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
  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) {
  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. }
  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. }
  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) {
  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) {
  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) }
  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) }
  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) }
  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) }
  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) }
  59. ©2019 Wantedly, Inc. Generate Kotlin Code with KotlinPoet

  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
  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” }
  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
  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
  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
  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
  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
  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
  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
  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
  70. ©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables:

    List<Pair<String, TypeElement?!>> = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder()
  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)
  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)
  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)
  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)
  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)
  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)
  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")
  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")
  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
  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
  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) } }
  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) } }
  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, } }
  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, } }
  85. ©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val

    codeBlockBuilder: CodeBlock.Builder = !!... val funSpec = FunSpec.builder("toMap") .addCode(codeBlockBuilder.build()) .build()
  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
  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
  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()
  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?> { !!... }
  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()
  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?> { !!... }
  92. ©2019 Wantedly, Inc. Generate Kotlin Code FunSpec, other options FunSpec.builder(!!...)

    .addKDoc(!!...) .addAnnotation(MyAnnotation!::class) .addParameter(ParameterSpec) .addModifiers(!!...) .beginControlFlow() .endControlFlow() .nextControlFlow(!!...) !!...
  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")
  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")
  95. ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec val className: ClassName

    = !!... val funSpec = FunSpec.builder("toMap") !!... FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) !!...
  96. ©2019 Wantedly, Inc. Generate Kotlin Code FileSpec val className: ClassName

    = !!... val funSpec = FunSpec.builder("toMap") !!... FileSpec.builder(className.packageName, className.simpleName) .addFunction(funSpec) !!...
  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”) !!...
  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)
  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)
  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)
  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 }
  102. ©2019 Wantedly, Inc. Testing Generated Code

  103. ©2019 Wantedly, Inc. BQQMJDBUJPO BOOPUBUJPO QSPDFTTPS Implementation kapt implementation Testing

    Generated Code Test module UFTU implementation kapt
  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"]) } }
  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"]) } }
  106. ©2019 Wantedly, Inc. Good for testing happy paths But unable

    to test and validate error cases Page Title Page Subtitle
  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
  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
  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() !!... }
  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() !!... }
  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) }
  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) }
  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) }
  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; } }
  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) }
  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) }
  117. ©2019 Wantedly, Inc. Things to Consider Limitations and drawbacks

  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
  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
  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
  121. ©2019 Wantedly, Inc. Should We Use Annotation Processor?

  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?
  123. ©2019 Wantedly, Inc. Interface and Abstract Class Sometimes they’re easier

    to implement Page Title Page Subtitle
  124. ©2019 Wantedly, Inc. Sample Annotation Processor at Wantedly

  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”, )
  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
  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