Slide 1

Slide 1 text

©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

Slide 2

Slide 2 text

©2019 Wantedly, Inc. Introduction Malvin Sutanto Software Engineer @Wantedly Android, Kotlin. Twitter/ Medium: @malvinsutanto

Slide 3

Slide 3 text

©2019 Wantedly, Inc. Metaprogramming

Slide 4

Slide 4 text

©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/

Slide 5

Slide 5 text

©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

Slide 6

Slide 6 text

©2019 Wantedly, Inc. Considered a bad practice and difficult to maintain Page Title Page Subtitle

Slide 7

Slide 7 text

©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

Slide 8

Slide 8 text

©2019 Wantedly, Inc. Dagger 2 Example Metaprogramming @Inject lateinit var repository: Repository !!... DaggerComponent.inject(this)

Slide 9

Slide 9 text

©2019 Wantedly, Inc. Annotation Processor

Slide 10

Slide 10 text

©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

Slide 11

Slide 11 text

©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 { val map: Map = 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 { val map: Map = mapOf( "streetName" to streetName, "city" to city ) return map }

Slide 12

Slide 12 text

©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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

©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” }

Slide 17

Slide 17 text

©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

Slide 18

Slide 18 text

©2019 Wantedly, Inc. Annotation Processor Dependency graph BQQMJDBUJPO BOOPUBUJPO QSPDFTTPS Implementation kapt implementation

Slide 19

Slide 19 text

©2019 Wantedly, Inc. AutoMapProcessor

Slide 20

Slide 20 text

©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

Slide 21

Slide 21 text

©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” }

Slide 22

Slide 22 text

©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 {

Slide 23

Slide 23 text

©2019 Wantedly, Inc. Kotlinx metadata • From JetBrains • https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm AutoMapProcessor Kotlinx metadata

Slide 24

Slide 24 text

©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }

Slide 25

Slide 25 text

©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }

Slide 26

Slide 26 text

©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }

Slide 27

Slide 27 text

©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

Slide 28

Slide 28 text

©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” }

Slide 29

Slide 29 text

©2019 Wantedly, Inc. AutoMapProcessor Processor class class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }

Slide 30

Slide 30 text

©2019 Wantedly, Inc. AutoMapProcessor Processor class annotated with AutoService @AutoService(Processor!::class) class AutoMapProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { override fun getSupportedAnnotationTypes(): Set = setOf(AutoMap!::class.java.canonicalName) override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { TODO("Process kotlin code here") } }

Slide 31

Slide 31 text

©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"

Slide 32

Slide 32 text

©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"

Slide 33

Slide 33 text

©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"

Slide 34

Slide 34 text

©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"

Slide 35

Slide 35 text

©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"

Slide 36

Slide 36 text

©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"

Slide 37

Slide 37 text

©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"

Slide 38

Slide 38 text

©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"

Slide 39

Slide 39 text

©2019 Wantedly, Inc. Processing Kotlin code

Slide 40

Slide 40 text

©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override fun process(annotations: Set, 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 }

Slide 41

Slide 41 text

©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override fun process(annotations: Set, 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 }

Slide 42

Slide 42 text

©2019 Wantedly, Inc. Processing Kotlin code Get annotated element override fun process(annotations: Set, 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)

Slide 43

Slide 43 text

©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)

Slide 44

Slide 44 text

©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 !!... }

Slide 45

Slide 45 text

©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 !!... }

Slide 46

Slide 46 text

©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 !!... }

Slide 47

Slide 47 text

©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 !!...

Slide 48

Slide 48 text

©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

Slide 49

Slide 49 text

©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) {

Slide 50

Slide 50 text

©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. }

Slide 51

Slide 51 text

©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. }

Slide 52

Slide 52 text

©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) {

Slide 53

Slide 53 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) {

Slide 54

Slide 54 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) }

Slide 55

Slide 55 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) }

Slide 56

Slide 56 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) }

Slide 57

Slide 57 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) }

Slide 58

Slide 58 text

©2019 Wantedly, Inc. Processing Kotlin code Reading class’ variables for (element in elements) { !!... !// Read the declared variables of each element. val variables: List> = element.enclosedElements .filterIsInstance() .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) }

Slide 59

Slide 59 text

©2019 Wantedly, Inc. Generate Kotlin Code with KotlinPoet

Slide 60

Slide 60 text

©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

Slide 61

Slide 61 text

©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” }

Slide 62

Slide 62 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 63

Slide 63 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 64

Slide 64 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 65

Slide 65 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 66

Slide 66 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 67

Slide 67 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 68

Slide 68 text

©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 69

Slide 69 text

©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 70

Slide 70 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder()

Slide 71

Slide 71 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)

Slide 72

Slide 72 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = !!... val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)

Slide 73

Slide 73 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", “Map") !// Map .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)

Slide 74

Slide 74 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)

Slide 75

Slide 75 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .plusParameter(String!::class.asTypeName()) .plusParameter(Any!::class.asTypeName().copy(nullable = true)) val codeBlockBuilder: CodeBlock.Builder = CodeBlock.builder() .addStatement("val map: %T = %M(", returnType, mapOfFunction)

Slide 76

Slide 76 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .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)

Slide 77

Slide 77 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .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")

Slide 78

Slide 78 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .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")

Slide 79

Slide 79 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block val variables: List> = !!... val returnType: ParameterizedTypeName = ClassName("kotlin.collections", "Map") !// Map .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

Slide 80

Slide 80 text

©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

Slide 81

Slide 81 text

©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) } }

Slide 82

Slide 82 text

©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) } }

Slide 83

Slide 83 text

©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, } }

Slide 84

Slide 84 text

©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, } }

Slide 85

Slide 85 text

©2019 Wantedly, Inc. Generate Kotlin Code Code block, cont’d val codeBlockBuilder: CodeBlock.Builder = !!... val funSpec = FunSpec.builder("toMap") .addCode(codeBlockBuilder.build()) .build()

Slide 86

Slide 86 text

©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 87

Slide 87 text

©2019 Wantedly, Inc. Generate Kotlin Code /** * Converts [Person] to [Map]. !*/ fun Person.toMap(): Map { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map } Sample

Slide 88

Slide 88 text

©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()

Slide 89

Slide 89 text

©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 { !!... }

Slide 90

Slide 90 text

©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()

Slide 91

Slide 91 text

©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 { !!... }

Slide 92

Slide 92 text

©2019 Wantedly, Inc. Generate Kotlin Code FunSpec, other options FunSpec.builder(!!...) .addKDoc(!!...) .addAnnotation(MyAnnotation!::class) .addParameter(ParameterSpec) .addModifiers(!!...) .beginControlFlow() .endControlFlow() .nextControlFlow(!!...) !!...

Slide 93

Slide 93 text

©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")

Slide 94

Slide 94 text

©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")

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

©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”) !!...

Slide 98

Slide 98 text

©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)

Slide 99

Slide 99 text

©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)

Slide 100

Slide 100 text

©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)

Slide 101

Slide 101 text

©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 { val map: Map = mapOf( "id" to id, "name" to name, "address" to address.toMap() ) return map }

Slide 102

Slide 102 text

©2019 Wantedly, Inc. Testing Generated Code

Slide 103

Slide 103 text

©2019 Wantedly, Inc. BQQMJDBUJPO BOOPUBUJPO QSPDFTTPS Implementation kapt implementation Testing Generated Code Test module UFTU implementation kapt

Slide 104

Slide 104 text

©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"]) } }

Slide 105

Slide 105 text

©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"]) } }

Slide 106

Slide 106 text

©2019 Wantedly, Inc. Good for testing happy paths But unable to test and validate error cases Page Title Page Subtitle

Slide 107

Slide 107 text

©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

Slide 108

Slide 108 text

©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

Slide 109

Slide 109 text

©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() !!... }

Slide 110

Slide 110 text

©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() !!... }

Slide 111

Slide 111 text

©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) }

Slide 112

Slide 112 text

©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) }

Slide 113

Slide 113 text

©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) }

Slide 114

Slide 114 text

©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; } }

Slide 115

Slide 115 text

©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) }

Slide 116

Slide 116 text

©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) }

Slide 117

Slide 117 text

©2019 Wantedly, Inc. Things to Consider Limitations and drawbacks

Slide 118

Slide 118 text

©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

Slide 119

Slide 119 text

©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

Slide 120

Slide 120 text

©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

Slide 121

Slide 121 text

©2019 Wantedly, Inc. Should We Use Annotation Processor?

Slide 122

Slide 122 text

©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?

Slide 123

Slide 123 text

©2019 Wantedly, Inc. Interface and Abstract Class Sometimes they’re easier to implement Page Title Page Subtitle

Slide 124

Slide 124 text

©2019 Wantedly, Inc. Sample Annotation Processor at Wantedly

Slide 125

Slide 125 text

©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 = listOf( "id", "name", “wanted_score”, “profile.introduction”, )

Slide 126

Slide 126 text

©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

Slide 127

Slide 127 text

©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