Pro Yearly is on sale from $80 to $50! »

Annotation Processors vs Kotlin Plugins

Annotation Processors vs Kotlin Plugins

2eba8c30e813c748916c692b9b231ee7?s=128

Bruno Aybar

May 28, 2020
Tweet

Transcript

  1. UNA BREVE INTRODUCCIÓN A KAPT & Kotlin Plugins

  2. @brunoaybarg Bruno125 Bruno Aybar Software Engineer @ Avantica Technologies AndroidDev

    Peru co-organizer https://brunoaybar.com/talks/intro-kapt-plugin
  3. 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
  4. 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!
  5. K A P T

  6. K A P T otlin nnotation rocessing ool

  7. Annotation @Entity public class Usuario { private String nombre; private

    int edad; ... }
  8. K A P T otlin nnotation rocessing ool

  9. K A P T otlin nnotation rocessing ool Java

  10. K A P T otlin nnotation rocessing ool Java

  11. KATP 1. Te permite CREAR nuevos archivos en base a

    las anotaciones 2. Funciona para archivos Java y Kotlin (kinda)
  12. Usuario.java Usuario.class

  13. Usuario.java Usuario.class javac

  14. @Entity public class Usuario { private String nombre; private int

    edad; ... } Usuario.class javac
  15. Usuario.java Usuario.class javac ANTES (pre JDK 5) javac: compilaba apt:

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

  17. Usuario.java Usuario.class javac annotation processing flow

  18. Usuario.java Usuario.class javac annotation processing flow 1. Identifica los procesadores

    de anotaciones ServiceLoader META-INF/services/ javax.annotation.processing.Processor busca
  19. 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
  20. annotation processing flow 1. Identifica los procesadores de anotaciones ServiceLoader

    META-INF/services/ javax.annotation.processing.Processor Usuario.java Usuario.class javac
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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)
  26. EJEMPLOS 1. Dagger 2. Spring 3. Android I. Room II.

    Lifecycle III.… 4. Gson / Jackson / Moshi 5. Etc.
  27. K-APT @Write fun annotateKotlinCode() { println("Puede ser engañoso...") }

  28. K-APT @Provides fun provideList(): List<Taco> { return emptyList() } @Inject

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

    lateinit var tacos: List<@SuppressJvmWildcard Taco>
  30. K-APT @Module class ConfigModule { @Provides static Config provideConfig() {

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

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

    ... } }
  33. K-APT @Module class ConfigModule { companion object { @JvmStatic @Provides

    fun provideConfig(): Config { ... } } }
  34. K-APT El procesador de anotaciones no recibe los archivos “.kt”

    directamente Recibe una representación intermedia del código
  35. K-APT Usuario.java UsuarioDao.java UsuarioTable.java Kotlin IR Annotation Processor

  36. K-APT data class Usuario(val permisos: List<Permisos>) vemos la "representación en

    Java"…
  37. 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; } }
  38. 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!
  39. K-APT Annotation Processing in a Kotlin World - Zac Sweers

    (KotlinConf 2018)
  40. Kotlin Compiler Plugin

  41. Arquitectura de un Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension

  42. Arquitectura de un Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension

    Writing Your First Kotlin Compiler Plugin - Kevin Most (KotlinConf 2018)
  43. Android Extensions Plugin Plugin Subplugin CommandLineProcessor ComponenRegistrar Extension Extension Generación

    de código
  44. Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

  45. Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

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

  47. Android Extensions Plugin Synthetic Imports Parcelable Implementation @Parcelize class MyClass

  48. LinearLayout Button EditText

  49. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

    LinearLayout Button EditText
  50. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

    activity_main.xml
  51. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

    LinearLayout Button EditText
  52. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

  53. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

  54. 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) }
  55. 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
  56. 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
  57. 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
  58. 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) }}
  59. 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
  60. Synthetic Import Way

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

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

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

    { ... } myButton.setOnClickListener { ... } } 2. Set layout
  64. 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
  65. No need to compile These fields are ready to be

    used as soon as you write them on your XML file
  66. (source code is inside Kotlin repo)

  67. We will analyse the source code of the plugin!

  68. Source code is written in Kotlin

  69. Analyse XML layouts, and generate fields Synthetic Imports During compilation,

    replaces fields with findViewById IDEA instructions to handle autocompletion, indexing, on-the-fly modifications
  70. CALLING findViewById BEHIND THE SCENES

  71. Which class defines findViewById? class View { public View findViewById(int

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

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

    id) { ... } }
  74. ResourcePropertyStackValue.kt Le vamos a dar instrucciones al compilador, para que

    modifique el código
  75. 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…
  76. 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") }
  77. 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 { ... } }
  78. 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") }
  79. } 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? }
  80. } FRAGMENT -> { v.invokevirtual("getView") v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> {

    v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should never occur else -> throw IllegalStateException("Invalid type") }
  81. } 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 }

  82. v.invokevirtual("findViewById") } LAYOUT_CONTAINER -> { v.invokeinterface("getContainerView") v.invokevirtual("findViewById") } // Should

    never occur else -> throw IllegalStateException("Invalid type") }
  83. 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") }
  84. Caching the Views

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

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

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

    { ... } findViewById(mainLayout).setOnClickListener { ... } } Inefficient!
  88. ANALYSING XML LAYOUTS

  89. 1. Selecting files to evaluate

  90. abstract class AndroidLayoutXmlFileManager(val project: Project) { open fun getModuleData(): AndroidModuleData

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

    { return AndroidModuleData( module = androidModule, variants = androidModule.variants.map { getVariantData(it) } ) } } getVariantData Evaluates each variant
  92. 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
  93. 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
  94. 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
  95. 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
  96. 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
  97. 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
  98. 2. Parsing XML files / components

  99. } 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 <XML tag>
  100. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

    } 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)
  101. <LinearLayout android:id="@+id/main_layout" ...> <EditText android:id="@+id/my_edit_text" ...> <Button android:id="@+id/my_button" ...> </LinearLayout>

    } 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)
  102. } 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)
  103. } 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" )
  104. } 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
  105. 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) } } }
  106. 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) } } } <LinearLayout android:id="@+id/main_layout" ...>
  107. 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
  108. 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) } } <view class=“android.widget.Button"...> <Button …/>
  109. 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) } } <view class=“android.widget.Button"...> <Button …/> Button
  110. 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) } } <view class=“android.widget.Button"...> <Button …/>
  111. 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
  112. 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) } } <LinearLayout android:id="@+id/main_layout" ...>
  113. 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...
  114. syntheticDescriptorGeneration.kt

  115. 3. Generating the properties

  116. 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,
  117. 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)
  118. /* isInline = */ false, CallableMemberDescriptor.Kind.SYNTHESIZED, /* original = */

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

    null, SourceElement.NO_SOURCE ) property.initialize(getter, null) return property }
  120. At this point, the compiler knows that the property exists

  121. 4. Generating packages to import

  122. 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") } } }
  123. // 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") } } }
  124. 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.*
  125. 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") } } }
  126. IDE instructions

  127. Always listening to changes in XML files Fields are always

    updated
  128. 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 } }
  129. 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
  130. 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)
  131. 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!
  132. } } val xmlAttribute = findXmlAttribute(child) ?: return val name

    = xmlAttribute.name if (name != "android:id" && name != "class") return incModificationCount() } } }
  133. } } 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
  134. 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
  135. A nivel de IDE… Implementa a mano el autocompletado Implementa

    “Ir a la declaración" Implementa la indexación de archivos Y más…
  136. 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
  137. 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
  138. 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
  139. KotlinJS?

  140. K-APT Usuario.java JS files / LLVM IR / etc. Kotlin

    IR Other processors Java Bytecode JVM processor
  141. KotlinJS? KotlinJS NO trabaja sobre la JVM… ... no soporta

    KAPT ... pero sí se le pueden agregar plugins
  142. KotlinJS? Ejemplo: Kotlin Serialization https://github.com/Kotlin/kotlinx.serialization

  143. @brunoaybarg Bruno125 Bruno Aybar Software Engineer @ Avantica Technologies AndroidDev

    Peru co-organizer Thanks! https://brunoaybar.com/talks/intro-kapt-plugin