Slide 1

Slide 1 text

UNA BREVE INTRODUCCIÓN A KAPT & Kotlin Plugins

Slide 2

Slide 2 text

@brunoaybarg Bruno125 Bruno Aybar Software Engineer @ Avantica Technologies AndroidDev Peru co-organizer https://brunoaybar.com/talks/intro-kapt-plugin

Slide 3

Slide 3 text

AGENDA 1. Introducción a KAPT A. KAPT vs APT B. Flujo de ejecución C. Ejemplos D. K in KAPT 2. Introducción a Kotlin Plugins A. Usos y Ejemplos B. Flujos de ejecución C. Deep dive: Android Extension Plugin 3. ¿Cuándo usar cada uno? EXTRA: KAPT y Plugins en el contexto de Kotlin/JS

Slide 4

Slide 4 text

OBJETIVOS 1. Entiendan los casos de uso de cada uno. 2. Se animen a investigar más, a contribuir a más proyectos open source!

Slide 5

Slide 5 text

K A P T

Slide 6

Slide 6 text

K A P T otlin nnotation rocessing ool

Slide 7

Slide 7 text

Annotation @Entity public class Usuario { private String nombre; private int edad; ... }

Slide 8

Slide 8 text

K A P T otlin nnotation rocessing ool

Slide 9

Slide 9 text

K A P T otlin nnotation rocessing ool Java

Slide 10

Slide 10 text

K A P T otlin nnotation rocessing ool Java

Slide 11

Slide 11 text

KATP 1. Te permite CREAR nuevos archivos en base a las anotaciones 2. Funciona para archivos Java y Kotlin (kinda)

Slide 12

Slide 12 text

Usuario.java Usuario.class

Slide 13

Slide 13 text

Usuario.java Usuario.class javac

Slide 14

Slide 14 text

@Entity public class Usuario { private String nombre; private int edad; ... } Usuario.class javac

Slide 15

Slide 15 text

Usuario.java Usuario.class javac ANTES (pre JDK 5) javac: compilaba apt: procesaba anotaciones

Slide 16

Slide 16 text

Usuario.java Usuario.class javac AHORA javac: compila y procesa anotaciones

Slide 17

Slide 17 text

Usuario.java Usuario.class javac annotation processing flow

Slide 18

Slide 18 text

Usuario.java Usuario.class javac annotation processing flow 1. Identifica los procesadores de anotaciones ServiceLoader META-INF/services/ javax.annotation.processing.Processor busca

Slide 19

Slide 19 text

annotation processing flow 1. Identifica los procesadores de anotaciones ServiceLoader com.example.EntityProcessor com.foo.OtherProcessor net.blabla.SpecialProcessor javax.annotation.processing.Processor Usuario.java Usuario.class javac

Slide 20

Slide 20 text

annotation processing flow 1. Identifica los procesadores de anotaciones ServiceLoader META-INF/services/ javax.annotation.processing.Processor Usuario.java Usuario.class javac

Slide 21

Slide 21 text

Usuario.java Usuario.class javac annotation processing flow 1. Identifica los procesadores de anotaciones 2. Cada procesador se encarga de procesar las anotaciones que necesite

Slide 22

Slide 22 text

Usuario.java Usuario.class javac annotation processing flow 1. Identifica los procesadores de anotaciones EntityProcessor UsuarioDao.java UsuarioTable.java crea crea 2. Cada procesador se encarga de procesar las anotaciones que necesite

Slide 23

Slide 23 text

Usuario.class javac annotation processing flow 1. Identifica los procesadores de anotaciones 2. Cada procesador se encarga de procesar las anotaciones que necesite UsuarioDao.java Usuario.java UsuarioTable.java

Slide 24

Slide 24 text

annotation processing flow 1. Identifica los procesadores de anotaciones 2. Cada procesador se encarga de procesar las anotaciones que necesite UsuarioDao.class Usuario.class UsuarioTable.class UsuarioDao.java Usuario.java UsuarioTable.java 3. Cuando no queden más anotaciones por procesar, se termina

Slide 25

Slide 25 text

1. Identifica los procesadores de anotaciones 2. Cada procesador se encarga de procesar las anotaciones que necesite 3. Cuando no queden más anotaciones por procesar, se termina Annotation Processing Boilerplate Destruction - Jake Wharton (droidconNYC 2014)

Slide 26

Slide 26 text

EJEMPLOS 1. Dagger 2. Spring 3. Android I. Room II. Lifecycle III.… 4. Gson / Jackson / Moshi 5. Etc.

Slide 27

Slide 27 text

K-APT @Write fun annotateKotlinCode() { println("Puede ser engañoso...") }

Slide 28

Slide 28 text

K-APT @Provides fun provideList(): List { return emptyList() } @Inject lateinit var tacos: List

Slide 29

Slide 29 text

K-APT @Provides fun provideList(): List { return emptyList() } @Inject lateinit var tacos: List<@SuppressJvmWildcard Taco>

Slide 30

Slide 30 text

K-APT @Module class ConfigModule { @Provides static Config provideConfig() { ... } }

Slide 31

Slide 31 text

K-APT @Module object ConfigModule { @Provides fun provideConfig(): Config { ... } }

Slide 32

Slide 32 text

K-APT @Module object ConfigModule { @Provides fun provideConfig(): Config { ... } }

Slide 33

Slide 33 text

K-APT @Module class ConfigModule { companion object { @JvmStatic @Provides fun provideConfig(): Config { ... } } }

Slide 34

Slide 34 text

K-APT El procesador de anotaciones no recibe los archivos “.kt” directamente Recibe una representación intermedia del código

Slide 35

Slide 35 text

K-APT Usuario.java UsuarioDao.java UsuarioTable.java Kotlin IR Annotation Processor

Slide 36

Slide 36 text

K-APT data class Usuario(val permisos: List) vemos la "representación en Java"…

Slide 37

Slide 37 text

K-APT d2 = {"Lcom/example/Usuario;", "", "permisos", "", "", "(Ljava/util/List;)V", "getPermisos", "()Ljava/util/List;", “main"} ) public final class Usuario { @NotNull private final List permisos; @NotNull public final List getPermisos() { return this.permisos; } public Usuario(@NotNull List permisos) { Intrinsics.checkParameterIsNotNull(permisos, "permisos"); super(); this.permisos = permisos; } }

Slide 38

Slide 38 text

K-APT @Metadata( mv = {1, 1, 15}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000\u0016\n\u0002\u0018\..."}, d2 = {"Lcom/example/Usuario;", "", "permisos", "", "", "(Ljava/util/List;)V", "getPermisos", "()Ljava/util/List;", “main"} ) public final class Usuario { @NotNull private final List permisos; @NotNull public final List getPermisos() { De aqui podemos sacar más data!

Slide 39

Slide 39 text

K-APT Annotation Processing in a Kotlin World - Zac Sweers (KotlinConf 2018)

Slide 40

Slide 40 text

Kotlin Compiler Plugin

Slide 41

Slide 41 text

Arquitectura de un Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension

Slide 42

Slide 42 text

Arquitectura de un Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension Writing Your First Kotlin Compiler Plugin - Kevin Most (KotlinConf 2018)

Slide 43

Slide 43 text

Android Extensions Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension Generación de código

Slide 44

Slide 44 text

Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

Slide 45

Slide 45 text

Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass Los plugins también pueden procesar anotaciones

Slide 46

Slide 46 text

Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

Slide 47

Slide 47 text

Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

Slide 48

Slide 48 text

LinearLayout Button EditText

Slide 49

Slide 49 text

LinearLayout Button EditText

Slide 50

Slide 50 text

activity_main.xml

Slide 51

Slide 51 text

LinearLayout Button EditText

Slide 52

Slide 52 text

Slide 53

Slide 53 text

Slide 54

Slide 54 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button) }

Slide 55

Slide 55 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button) } 1. Declare variables

Slide 56

Slide 56 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button) } 2. Set layout

Slide 57

Slide 57 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button) } 3. Assign variables

Slide 58

Slide 58 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button) }}

Slide 59

Slide 59 text

lateinit var mainLayout: LinearLayout lateinit var myEditText: EditText lateinit var myButton: Button fun onCreate() { setContentView(R.layout.activity_main) mainLayout = findViewById(R.id.main_layout) myEditText = findViewById(R.id.my_edit_text) myButton = findViewById(R.id.my_button)
 
 mainLayout.orientation = VERTICAL
 myEditText.setOnTextChangedListener { … } myButton.setOnClickListener { … } }} 4. Actually use your components

Slide 60

Slide 60 text

Synthetic Import Way

Slide 61

Slide 61 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... myEditText.setOnTexChangedListener { ... } myButton.setOnClickListener { ... } }

Slide 62

Slide 62 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... myEditText.setOnTexChangedListener { ... } myButton.setOnClickListener { ... } } 1. Import your layout

Slide 63

Slide 63 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... myEditText.setOnTexChangedListener { ... } myButton.setOnClickListener { ... } } 2. Set layout

Slide 64

Slide 64 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... myEditText.setOnTexChangedListener { ... } myButton.setOnClickListener { ... } } 3. Use your components right away

Slide 65

Slide 65 text

No need to compile These fields are ready to be used as soon as you write them on your XML file

Slide 66

Slide 66 text

(source code is inside Kotlin repo)

Slide 67

Slide 67 text

We will analyse the source code of the plugin!

Slide 68

Slide 68 text

Source code is written in Kotlin

Slide 69

Slide 69 text

Analyse XML layouts, and generate fields Synthetic Imports During compilation, replaces fields with findViewById IDEA instructions to handle autocompletion, indexing, on-the-fly modifications

Slide 70

Slide 70 text

CALLING findViewById BEHIND THE SCENES

Slide 71

Slide 71 text

Which class defines findViewById? class View { public View findViewById(int id) { ... } }

Slide 72

Slide 72 text

Which class defines findViewById? class Activity { public View findViewById(int id) { ... } }

Slide 73

Slide 73 text

Which class defines findViewById? class Dialog { public View findViewById(int id) { ... } }

Slide 74

Slide 74 text

ResourcePropertyStackValue.kt Le vamos a dar instrucciones al compilador, para que modifique el código

Slide 75

Slide 75 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... myEditText.setOnTexChangedListener { ... } myButton.setOnClickListener { ... } } Cada vez que invoques uno de estos…

Slide 76

Slide 76 text

when (containerType) { ACTIVITY, VIEW, DIALOG -> { v.invokevirtual("findViewById") } FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }

Slide 77

Slide 77 text

when (containerType) { ACTIVITY, VIEW, DIALOG -> { v.invokevirtual("findViewById") } FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") } class MyFragment: Fragment() { override fun onCreateView(): View { ... } }

Slide 78

Slide 78 text

when (containerType) { ACTIVITY, VIEW, DIALOG -> { v.invokevirtual("findViewById") } FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }

Slide 79

Slide 79 text

} FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") } public interface LayoutContainer { /** Returns the root holder view. */ public val containerView: View? }

Slide 80

Slide 80 text

} FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }

Slide 81

Slide 81 text

} FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") } *Only works if you enable* androidExtensions { experimental = true }


Slide 82

Slide 82 text

v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }

Slide 83

Slide 83 text

when (containerType) { ACTIVITY, VIEW, DIALOG -> { v.invokevirtual("findViewById") } FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }

Slide 84

Slide 84 text

Caching the Views

Slide 85

Slide 85 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) mainLayout.orientation = ... mainLayout.setOnTouchListener { ... } mainLayout.setOnClickListener { ... } }

Slide 86

Slide 86 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) findViewById(mainLayout).orientation = ... findViewById(mainLayout).setOnTouchListener { ... } findViewById(mainLayout).setOnClickListener { ... } }

Slide 87

Slide 87 text

import kotlinx.android.synthetic.main.activity_main.* fun onCreate() { setContentView(R.layout.activity_main) findViewById(mainLayout).orientation = ... findViewById(mainLayout).setOnTouchListener { ... } findViewById(mainLayout).setOnClickListener { ... } } Inefficient!

Slide 88

Slide 88 text

ANALYSING XML LAYOUTS

Slide 89

Slide 89 text

1. Selecting files to evaluate

Slide 90

Slide 90 text

abstract class AndroidLayoutXmlFileManager(val project: Project) { open fun getModuleData(): AndroidModuleData { return AndroidModuleData( module = androidModule, variants = androidModule.variants.map { getVariantData(it) } ) } }

Slide 91

Slide 91 text

abstract class AndroidLayoutXmlFileManager(val project: Project) { open fun getModuleData(): AndroidModuleData { return AndroidModuleData( module = androidModule, variants = androidModule.variants.map { getVariantData(it) } ) } } getVariantData Evaluates each variant

Slide 92

Slide 92 text

private fun getVariantData(variant: Variant): AndroidVariantData { val resDirectories = variant.resDirectories.map { fileManager.findFileByUrl("file://$it") } val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() } val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } getVariantData

Slide 93

Slide 93 text

private fun getVariantData(variant: Variant): AndroidVariantData { val resDirectories = variant.resDirectories.map { fileManager.findFileByUrl("file://$it") } val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() } val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } 1. Searches “res" folder

Slide 94

Slide 94 text

private fun getVariantData(variant: Variant): AndroidVariantData { val resDirectories = variant.resDirectories.map { fileManager.findFileByUrl("file://$it") } val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() } val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } 2. Looks at each file

Slide 95

Slide 95 text

fileManager.findFileByUrl("file://$it") } val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() } val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } return AndroidVariantData(variant, layoutNameToXmlFiles) } 3. Filter XML layouts files

Slide 96

Slide 96 text

it?.getAllChildren() ?: listOf() } val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } return AndroidVariantData(variant, layoutNameToXmlFiles) } 4. Gets the names of the files

Slide 97

Slide 97 text

val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") } val layoutNameToXmlFiles = allLayoutFiles .groupBy { it.name.substringBeforeLast('.') } .mapValues { it.value.sortedBy { it.parent.name.length } } return AndroidVariantData(variant, layoutNameToXmlFiles) } 5. We know which files to evaluate

Slide 98

Slide 98 text

2. Parsing XML files / components

Slide 99

Slide 99 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) 1. Evaluates each

Slide 100

Slide 100 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue)

Slide 101

Slide 101 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue)

Slide 102

Slide 102 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue)

Slide 103

Slide 103 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) val IGNORED_XML_WIDGET_TYPES = setOf( "requestFocus", "merge", "tag", "check", "blink" )

Slide 104

Slide 104 text

} override fun visitXmlTag(tag: XmlTag?) { val localName = tag?.localName ?: "" if (isWidgetTypeIgnored(localName)) { tag?.acceptChildren(this) return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) 2. Ignores unsupported widgets

Slide 105

Slide 105 text

return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } }

Slide 106

Slide 106 text

return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } }

Slide 107

Slide 107 text

return } val idAttribute = tag?.getAttribute(ID_ATTRIBUTE) if (idAttribute != null) { val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } 3. Only considers widgets with an ID

Slide 108

Slide 108 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } }

Slide 109

Slide 109 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } } Button

Slide 110

Slide 110 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } }

Slide 111

Slide 111 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } } 4. Determines the widget type

Slide 112

Slide 112 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } }

Slide 113

Slide 113 text

val idAttributeValue = idAttribute.value if (idAttributeValue != null) { val xmlType = tag.getAttribute(CLASS_ATTRIBUTE) ?.value ?: localName val name = androidIdToName(idAttributeValue) if (name != null) { elementCallback(name, xmlType, idAttribute) } } } tag?.acceptChildren(this) } } 5. We found a valid widget! Now to the next step...

Slide 114

Slide 114 text

syntheticDescriptorGeneration.kt

Slide 115

Slide 115 text

3. Generating the properties

Slide 116

Slide 116 text

private fun genProperty(...): PropertyDescriptor { val property = object : AndroidSyntheticProperty ( ..., Modality.FINAL, Visibilities.PUBLIC, Name.identifier(resource.id.name), CallableMemberDescriptor.Kind.SYNTHESIZED, /* lateInit = */ false, /* isConst = */ false, /* isExternal = */ false, ) private fun genProperty(...): PropertyDescriptor { val property = object : AndroidSyntheticProperty ( ..., Modality.FINAL, Visibilities.PUBLIC, Name.identifier(resource.id.name), CallableMemberDescriptor.Kind.SYNTHESIZED, /* lateInit = */ false, /* isConst = */ false, /* isExternal = */ false, ) val getter = PropertyGetterDescriptorImpl( property,

Slide 117

Slide 117 text

val getter = PropertyGetterDescriptorImpl( property, Annotations.EMPTY, Modality.FINAL, Visibilities.PUBLIC, /* isDefault = */ false, /* isExternal = */ false, /* isInline = */ false, CallableMemberDescriptor.Kind.SYNTHESIZED, /* original = */ null, SourceElement.NO_SOURCE ) property.initialize(getter, null)

Slide 118

Slide 118 text

/* isInline = */ false, CallableMemberDescriptor.Kind.SYNTHESIZED, /* original = */ null, SourceElement.NO_SOURCE ) property.initialize(getter, null) return property } Getter only, no setter

Slide 119

Slide 119 text

/* isInline = */ false, CallableMemberDescriptor.Kind.SYNTHESIZED, /* original = */ null, SourceElement.NO_SOURCE ) property.initialize(getter, null) return property }

Slide 120

Slide 120 text

At this point, the compiler knows that the property exists

Slide 121

Slide 121 text

4. Generating packages to import

Slide 122

Slide 122 text

override fun getPackageFragmentProvider(...) { // Packages with synthetic properties for (variantData in moduleData.variants) { val variant = variantData.variant.name for (layoutName in variantData.layouts) { val packageName = "$SYNTHETIC_PACKAGE.$variant.$layoutName" createPackageFragment(packageFqName) createPackageFragment(packageFqName + “.view") } } }

Slide 123

Slide 123 text

// Packages with synthetic properties for (variantData in moduleData.variants) { val variant = variantData.variant.name for (layoutName in variantData.layouts) { val packageName = "$SYNTHETIC_PACKAGE.$variant.$layoutName" createPackageFragment(packageFqName) createPackageFragment(packageFqName + “.view") } } }

Slide 124

Slide 124 text

val variant = variantData.variant.name for (layoutName in variantData.layouts) { val packageName = "$SYNTHETIC_PACKAGE.$variant.$layoutName" createPackageFragment(packageFqName) createPackageFragment(packageFqName + “.view") } } } import kotlinx.android.synthetic.main.activity_main.*
 
 import kotlinx.android.synthetic.main.activity_main.view.*

Slide 125

Slide 125 text

override fun getPackageFragmentProvider(...) { // Packages with synthetic properties for (variantData in moduleData.variants) { val variant = variantData.variant.name for (layoutName in variantData.layouts) { val packageName = "SYNTHETIC_PACKAGE.$variant.$layoutName" createPackageFragment(packageFqName) createPackageFragment(packageFqName + “.view") } } }

Slide 126

Slide 126 text

IDE instructions

Slide 127

Slide 127 text

Always listening to changes in XML files Fields are always updated

Slide 128

Slide 128 text

class AndroidPsiTreeChangePreprocessor { override fun treeChanged(event: PsiTreeChangeEventImpl) { if (event.code in HANDLED_EVENTS) { // Layout file was renamed val element = event.element if (element != null && event.code == PROPERTY_CHANGED && event.propertyName == "fileName") { if (checkIfLayoutFile(element)) { incModificationCount() return } }

Slide 129

Slide 129 text

if (event.code in HANDLED_EVENTS) { // Layout file was renamed val element = event.element if (element != null && event.code == PROPERTY_CHANGED && event.propertyName == "fileName") { if (checkIfLayoutFile(element)) { incModificationCount() return } } val xmlAttribute = findXmlAttribute(child) ?: return val name = xmlAttribute.name if (name != "android:id" && name != "class") return If file was: - Added - Removed - Moved - Replaced - Renamed

Slide 130

Slide 130 text

if (event.code in HANDLED_EVENTS) { // Layout file was renamed val element = event.element if (element != null && event.code == PROPERTY_CHANGED && event.propertyName == "fileName") { if (checkIfLayoutFile(element)) { incModificationCount() return } } val xmlAttribute = findXmlAttribute(child) ?: return val name = xmlAttribute.name if (name != "android:id" && name != "class") return And is an XML layout (inside res/layout folder)

Slide 131

Slide 131 text

if (event.code in HANDLED_EVENTS) { // Layout file was renamed val element = event.element if (element != null && event.code == PROPERTY_CHANGED && event.propertyName == "fileName") { if (checkIfLayoutFile(element)) { incModificationCount() return } } val xmlAttribute = findXmlAttribute(child) ?: return val name = xmlAttribute.name if (name != "android:id" && name != "class") return Layout should be re-evaluated!

Slide 132

Slide 132 text

} } val xmlAttribute = findXmlAttribute(child) ?: return val name = xmlAttribute.name if (name != "android:id" && name != "class") return incModificationCount() } } }

Slide 133

Slide 133 text

} } val xmlAttribute = findXmlAttribute(child) ?: return val name = xmlAttribute.name if (name != "android:id" && name != "class") return incModificationCount() } } } Only updated if id or class is modified

Slide 134

Slide 134 text

El plugin ha tenido que: Saber qué archivos analizar Saber cómo analizar cada archivo Saber cómo manejar cada componente Saber en qué momentos actualizar Manipular el código interno Crear imports dinámicamente

Slide 135

Slide 135 text

A nivel de IDE… Implementa a mano el autocompletado Implementa “Ir a la declaración" Implementa la indexación de archivos Y más…

Slide 136

Slide 136 text

Más detalle en... Analyzing the Internals of Kotlin’s Android Syntethic Import (Part 1 & 2) https://brunoaybar.com/kotlin-android-synthetic- import-part-1 https://brunoaybar.com/kotlin-android-synthetic- import-part-2

Slide 137

Slide 137 text

KAPT -Trabajan sobre Java y Kotlin -Solo sobre la JVM -Solo puede crear archivos -Son relativamente sencillos de crear -El código existe LUEGO de compilar

Slide 138

Slide 138 text

Kotlin Plugin -Puedes hacer lo que quieras: modificar codigo, crear nuevos archivos, comunicarte con el IDE, etc. -Son multiplataforma -Demandan DEMASIADO trabajo -Documentación casi nula -Por ahora, solo hay plugins desarrollados por JetBrains -El código existe LUEGO de compilar… pero puede integrarse en tiempo real con el IDE

Slide 139

Slide 139 text

KotlinJS?

Slide 140

Slide 140 text

K-APT Usuario.java JS files / LLVM IR / etc. Kotlin IR Other processors Java Bytecode JVM processor

Slide 141

Slide 141 text

KotlinJS? KotlinJS NO trabaja sobre la JVM… ... no soporta KAPT ... pero sí se le pueden agregar plugins

Slide 142

Slide 142 text

KotlinJS? Ejemplo: Kotlin Serialization https://github.com/Kotlin/kotlinx.serialization

Slide 143

Slide 143 text

@brunoaybarg Bruno125 Bruno Aybar Software Engineer @ Avantica Technologies AndroidDev Peru co-organizer Thanks! https://brunoaybar.com/talks/intro-kapt-plugin