Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ricardo Lage - From_AutoValue_to_Data_Classes_2

Ricardo Lage - From_AutoValue_to_Data_Classes_2

droidcon Berlin

July 17, 2018
Tweet

More Decks by droidcon Berlin

Other Decks in Programming

Transcript

  1. public class User { } String uuid; String login; String

    name; String email; String extraInfo;
  2. public final class User { } String uuid; String login;

    String name; String email; String extraInfo; private final private final private final private final private final
  3. public final class User { } public User(String uuid, String

    login, String name, String email, String extraInfo) { this.uuid = uuid; this.login = login; this.name = name; this.email = email; this.extraInfo = extraInfo; } String uuid; String login; String name; String email; String extraInfo; private final private final private final private final private final
  4. public final class User { } public User(String uuid, String

    login, String name, String email, @Nullable String extraInfo) { this.uuid = uuid; this.login = login; this.name = name; this.email = email; this.extraInfo = extraInfo; } String uuid; String login; String name; String email; String extraInfo; private final private final private final private final private final
  5. @Override public boolean equals(Object o) { if (this == o)

    return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (!uuid.equals(user.uuid)) return false; if (!login.equals(user.login)) return false; if (!name.equals(user.name)) return false; if (!email.equals(user.email)) return false; if (extraInfo != null ? !extraInfo.equals(user.extraInfo) : user.extraInfo != null) return false; return true; } @Override public int hashCode() { int result = uuid.hashCode(); result = 31 * result + login.hashCode(); result = 31 * result + name.hashCode(); result = 31 * result + email.hashCode(); result = 31 * result + (extraInfo != null ? extraInfo.hashCode() : 0); return result; }
  6. public String getUuid() { return uuid; } public String getLogin()

    { return login; } public String getName() { return name; } public String getEmail() { return email; } public String getExtraInfo() { return extraInfo; }
  7. public final class User implements Parcelable { private final String

    uuid; private final String login; private final String name; private final String email; private final String extraInfo; public User(String uuid, String login, String name, String email, @Nullable String extraInfo) { this.uuid = uuid; this.login = login; this.name = name; this.email = email; this.extraInfo = extraInfo; } public User(Parcel in) { uuid = in.readString(); login = in.readString(); name = in.readString(); email = in.readString(); extraInfo = in.readString(); }
  8. And that’s not all • Validate non-null constructor parameters •

    Add toString() implementation • Ensure consistency when adding/removing fields • Rely on reflection or write your own adapter for serialization • Rinse & repeat for every new such classes
  9. • Easier to create & maintain value classes • Automatically

    generates equals(), hashCode() and toString() for you • Plugin system for additional code generation: Parcelable implementation, Gson type adapters, etc. AutoValue to the rescue
  10. Why use AutoValue? • No runtime dependencies • API-invisible -

    The class functions like any other • Negligible cost to performance • Very few limitations on what your class can do • Extralinguistic "magic" kept to an absolute minimum (uses only standard Java platform technologies, in the manner they were intended) Source: https://github.com/google/auto/blob/master/value/ userguide/why.md
  11. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); }
  12. // Generated by com.google.auto.value.processor.AutoValueProcessor final class AutoValue_User extends User {

    private final String uuid; private final String login; private final String name; private final String email; private final String extraInfo; AutoValue_User( String uuid, String login, String name, String email, @Nullable String extraInfo) { if (uuid == null) { throw new NullPointerException("Null uuid"); } this.uuid = uuid; if (login == null) { throw new NullPointerException("Null login"); } this.login = login;
  13. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); }
  14. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } }
  15. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } }
  16. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } }
  17. @AutoValue public abstract class User implements Parcelable { public abstract

    String uuid(); public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public abstract User withExtraInfo(String extraInfo); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } public static TypeAdapter<User> typeAdapter(Gson gson) { return new AutoValue_User.GsonTypeAdapter(gson); }
  18. @AutoValue public abstract class User implements Parcelable { public abstract

    String uuid(); public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public abstract User withExtraInfo(String extraInfo); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } public static TypeAdapter<User> typeAdapter(Gson gson) { return new AutoValue_User.GsonTypeAdapter(gson); }
  19. @AutoValue public abstract class User implements Parcelable { public abstract

    String uuid(); public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public abstract User withExtraInfo(String extraInfo); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } public static TypeAdapter<User> typeAdapter(Gson gson) { return new AutoValue_User.GsonTypeAdapter(gson); }
  20. // Generated by com.google.auto.value.processor.AutoValueProcessor abstract class $$$AutoValue_User extends User {

    private final String uuid; private final String login; private final String name; private final String email; private final String extraInfo; $$$AutoValue_User( String uuid, String login, String name, String email, @Nullable String extraInfo) { if (uuid == null) { throw new NullPointerException("Null uuid"); } this.uuid = uuid; if (login == null) { throw new NullPointerException("Null login"); } this.login = login;
  21. abstract class $$AutoValue_User extends $$$AutoValue_User { $$AutoValue_User(String uuid, String login,

    String name, String email, @Nullable String extraInfo) { super(uuid, login, name, email, extraInfo); } public static final class GsonTypeAdapter extends TypeAdapter<User> { private final TypeAdapter<String> string_adapter; public GsonTypeAdapter(Gson gson) { this.string_adapter = gson.getAdapter(String.class); } @Override public void write(JsonWriter jsonWriter, User object) throws IOException { if (object == null) { jsonWriter.nullValue(); return; } jsonWriter.beginObject(); jsonWriter.name("uuid"); string_adapter.write(jsonWriter, object.uuid()); jsonWriter.name("login"); string_adapter.write(jsonWriter, object.login()); jsonWriter.name("name");
  22. abstract class $AutoValue_User extends $$AutoValue_User { $AutoValue_User(String uuid, String login,

    String name, String email, String extraInfo) { super(uuid, login, name, email, extraInfo); } @Override public final User withExtraInfo(String extraInfo) { return new AutoValue_User(uuid(), login(), name(), email(), extraInfo); } }
  23. final class AutoValue_User extends $AutoValue_User { public static final Parcelable.Creator<AutoValue_User>

    CREATOR = new Parcelable.Creator<AutoValue_User>() { @Override public AutoValue_User createFromParcel(Parcel in) { return new AutoValue_User( in.readString(), in.readString(), in.readString(), in.readString(), in.readInt() == 0 ? in.readString() : null ); } @Override public AutoValue_User[] newArray(int size) { return new AutoValue_User[size]; } }; AutoValue_User(String uuid, String login, String name, String email, @Nullable String extraInfo) { super(uuid, login, name, email, extraInfo); }
  24. @AutoValue public abstract class User { public abstract String uuid();

    public abstract String login(); public abstract String name(); public abstract String email(); @Nullable public abstract String extraInfo(); public static final User create(String login, String name, String email) { return new AutoValue_User(UUID.randomUUID().toString(), login, name, email, null); } }
  25. @AutoValue abstract class User : Parcelable { abstract fun uuid():

    String abstract fun login(): String abstract fun name(): String abstract fun email(): String abstract fun extraInfo(): String? abstract fun withExtraInfo(extraInfo: String): User companion object { fun typeAdapter(gson: Gson): TypeAdapter<User> = `$ $AutoValue_User`.GsonTypeAdapter(gson) fun create(login: String, name: String, email: String): User = AutoValue_User(UUID.randomUUID().toString(), login, name, email, null) } }
  26. Data Classes • Kotlin’s answer to value classes: equals(), hashCode()

    and toString() derived from the constructor: data class User(val uuid: String, val login: String, 
 val name: String, val email: String, val extraInfo: String?) • Built-in copy() function: 
 val userWithInfo = user.copy(extraInfo = "Hello World") • Destructuring declarations: 
 val (_, login, name) = user

  27. Data classes vs AutoValue • Less external dependencies • Simpler/smaller

    code • No “open” abstract classes • Faster build / less overhead from annotation processors • Plugin issues
  28. Plugin issues • Yet another external dependencies • "with" requires

    custom methods for each configuration:
 
 
 
 • Gson plugin requires @SerializedName for all camel cased fields. E.g., 
 abstract fun withExtraInfo(extraInfo: String): User abstract fun withExtraInfoAndEmail(extraInfo: String, email: String): User @SerializedName("extra_info") abstract fun extraInfo(): String?
  29. @AutoValue abstract class User : Parcelable { abstract fun uuid():

    String abstract fun login(): String abstract fun name(): String abstract fun email(): String abstract fun extraInfo(): String? abstract fun withExtraInfo(extraInfo: String): User companion object { @JvmStatic fun typeAdapter(gson: Gson): TypeAdapter<User> = `$ $AutoValue_User`.GsonTypeAdapter(gson) @JvmStatic fun create(login: String, name: String, email: String): User = AutoValue_User(UUID.randomUUID().toString(), login, name, email, null) } } In other words. We are moving from this:
  30. @Parcelize data class User(val uuid: String, val login: String, 


    val name: String, val email: String, val extraInfo: String?): Parcelable To this:
  31. Parcelize plugin • Kotlin’s first class support for Parcelable
 https://github.com/Kotlin/KEEP/blob/master/proposals/extensions/android-

    parcelable.md • Marked as experimental but no issues present on the latest Kotlin version • A compiler plugin
 On AS: Tools → Kotlin → Show Kotlin Bytecode → Decompile button
  32. Parcelize plugin • Kotlin’s first class support for Parcelable
 https://github.com/Kotlin/KEEP/blob/master/proposals/extensions/android-

    parcelable.md • Marked as experimental but no issues present on the latest Kotlin version • A compiler plugin
 On AS: Tools → Kotlin → Show Kotlin Bytecode → Decompile button
  33. @Parcelize public final class User implements Parcelable { @NotNull private

    final String uuid; @NotNull private final String login; @NotNull private final String name; @NotNull private final String email; @Nullable private final String extraInfo; public static final User.Creator CREATOR = new User.Creator(); @NotNull public final String getUuid() { return this.uuid; } @NotNull public final String getLogin() { User class decompiled to Java from our data class
  34. Non-optional fields and Gson @Parcelize data class User(val uuid: String,

    val login: String, 
 val name: String, val email: String, val extraInfo: String?): Parcelable • When de-serializing a User object, what if “login" is not present in the response?
  35. Non-optional fields and Gson @Parcelize data class User(val uuid: String,

    val login: String, 
 val name: String, val email: String, val extraInfo: String?): Parcelable • When de-serializing a User object, what if “login" is not present in the response? • Program is going to fail in unexpected ways when we try to use the “login" field.
  36. Serialization • Gson - The classic
 https://github.com/google/gson • Moshi -

    The modern
 https://github.com/square/moshi
 - Faster than Gson & without its quirks
 - Kotlin support (Kotlin reflection or CodeGen)
  37. GsonValue @Parcelize @GsonConstructor data class User (val uuid: String, val

    login: String, val name: String, val email: String, @SerializedName("extra_info") val extraInfo: String?): Parcelable
  38. GsonValue @Parcelize @GsonConstructor data class User (val uuid: String, val

    login: String, val name: String, val email: String, @SerializedName("extra_info") val extraInfo: String?): Parcelable
  39. Kotshi @Parcelize @JsonSerializable data class User (val uuid: String, val

    login: String, val name: String, val email: String, @Json(name = “extra_info") val extraInfo: String?): Parcelable
  40. Kotshi @Parcelize @JsonSerializable data class User (val uuid: String, val

    login: String, val name: String, val email: String, @Json(name = “extra_info") val extraInfo: String?): Parcelable
  41. Serialization • Gson - The classic
 https://github.com/google/gson • Moshi -

    The modern
 https://github.com/square/moshi
 - Faster than Gson & without its quirks
 - Kotlin support (Kotlin reflection or CodeGen) • Kotlinx.serialization (KSerializer) - The new kid
 https://github.com/Kotlin/kotlinx.serialization
  42. KSerializer @Parcelize @Serializable data class User (val uuid: String, val

    login: String, val name: String, val email: String, @SerialName(“extra_info") val extraInfo: String?): Parcelable
  43. KSerializer @Parcelize @Serializable data class User (val uuid: String, val

    login: String, val name: String, val email: String, @SerialName(“extra_info") val extraInfo: String?): Parcelable
  44. @kotlinx.serialization.Serializable public final data class User public constructor(uuid: kotlin.String, login:

    kotlin.String, name: kotlin.String, email: kotlin.String, extraInfo: kotlin.String?) { public companion object { } public final val email: kotlin.String /* compiled code */ @kotlinx.serialization.SerialName public final val extraInfo: kotlin.String? /* compiled code */ public final val login: kotlin.String /* compiled code */ public final val name: kotlin.String /* compiled code */ public final val uuid: kotlin.String /* compiled code */ ... public object `$serializer` : kotlinx.serialization.KSerializer<User> { } }
  45. @kotlinx.serialization.Serializable public final data class User public constructor(uuid: kotlin.String, login:

    kotlin.String, name: kotlin.String, email: kotlin.String, extraInfo: kotlin.String?) { public companion object { } public final val email: kotlin.String /* compiled code */ @kotlinx.serialization.SerialName public final val extraInfo: kotlin.String? /* compiled code */ public final val login: kotlin.String /* compiled code */ public final val name: kotlin.String /* compiled code */ public final val uuid: kotlin.String /* compiled code */ ... public object `$serializer` : kotlinx.serialization.KSerializer<User> { } }
  46. jvm/Serialization.kt actual fun <T: Any> KClass<T>.serializer(): KSerializer<T> = this.java.invokeSerializerGetter() ?:

    throw SerializationException("Can't locate default serializer for class $this")
  47. jvm/Serialization.kt actual fun <T: Any> KClass<T>.serializer(): KSerializer<T> = this.java.invokeSerializerGetter() ?:

    throw SerializationException("Can't locate default serializer for class $this")
  48. jvm/Serialization.kt actual fun <T: Any> KClass<T>.serializer(): KSerializer<T> = this.java.invokeSerializerGetter() ?:

    throw SerializationException("Can't locate default serializer for class $this") • Example with Retrofit adapter:
 https://github.com/JakeWharton/SdkSearch (Kotlin multiplatform, coroutines and serialization)
 https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter
  49. Serialization • Gson vs Moshi vs Kotlinx Serialization… Oh my!

    • Field name policy issue: All libraries and plugins considered require some form of @SerializedName(“extra_info”) annotation to all camel cased fields • KSerializer: Multiplatform, no reflection, compiler plugin, more flexible adapters, but is its parsing faster?
  50. • Benchmark score using JMH (higher score is better)
 http://openjdk.java.net/projects/code-tools/jmh/

    • Original work comparing Moshi and Gson by Zac Sweers
 https://github.com/hzsweers/json-serialization-benchmarking • Our solution (at the time): Reflection + GsonValue adapters (for things that impact startup performance or APIs that we don’t control) Benchmark Mode Cnt Score Error Units
 moshi_reflective_fromJson thrpt 200 771.601 ± 8.017 ops/s gson_reflective_fromJson thrpt 200 637.594 ± 11.412 ops/s kserializer_fromJson thrpt 200 384.018 ± 3.459 ops/s moshi_reflective_toJson thrpt 200 1202.966 ± 6.690 ops/s gson_reflective_toJson thrpt 200 753.715 ± 18.660 ops/s kserializer_toJson thrpt 200 403.390 ± 1.846 ops/s Not so fast? 
 (results back in February)
  51. Just as fast now • Benchmark score using JMH (higher

    score is better)
 http://openjdk.java.net/projects/code-tools/jmh/ • Original work comparing Moshi and Gson by Zac Sweers
 https://github.com/hzsweers/json-serialization-benchmarking • Benchmark Mode Cnt Score Error Units
 moshi_reflective_fromJson thrpt 200 854.247 ± 6.300 ops/s gson_reflective_fromJson thrpt 200 1385.616 ± 14.821 ops/s kserializer_fromJson thrpt 200 1107.929 ± 51.456 ops/s moshi_reflective_toJson thrpt 200 1207.557 ± 8.226 ops/s gson_reflective_toJson thrpt 200 957.015 ± 4.439 ops/s kserializer_toJson thrpt 200 1175.760 ± 17.874 ops/s
  52. Just as fast now • Comparing with AutoValue: Benchmark Mode

    Cnt Score Error Units
 moshi_reflective_fromJson thrpt 200 854.247 ± 6.300 ops/s moshi_autovalue_fromJson thrpt 200 887.876 ± 6.539 ops/s gson_reflective_fromJson thrpt 200 1385.616 ± 14.821 ops/s gson_autovalue_fromJson thrpt 200 1435.158 ± 18.064 ops/s kserializer_fromJson thrpt 200 1107.929 ± 51.456 ops/s moshi_reflective_toJson thrpt 200 1207.557 ± 8.226 ops/s moshi_autovalue_toJson thrpt 200 1478.233 ± 2.913 ops/s gson_reflective_toJson thrpt 200 957.015 ± 4.439 ops/s gson_autovalue_toJson thrpt 200 1197.856 ± 2.830 ops/s kserializer_toJson thrpt 200 1175.760 ± 17.874 ops/s
  53. Migration Workflow • Convert from Java to Kotlin: Still an

    @AutoValue abstract class • Tedious and error prone process of manually converting each class to data class • In Java, accessing a data class field requires prefixing it with “get”
  54. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { //do something } return super.visitClass(klass, data) } }) } }
  55. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { //do something } return super.visitClass(klass, data) } }) } }
  56. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { //do something } return super.visitClass(klass, data) } }) } }
  57. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { //do something } return super.visitClass(klass, data) } }) } }
  58. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { //do something } return super.visitClass(klass, data) } }) } }
  59. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { write.run<Throwable> { } return super.visitClass(klass, data) } }) } }
  60. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { if (!klass.isData()) { write.run<Throwable> { klass.addModifier(KtTokens.DATA_KEYWORD) if (klass.isAbstract()) { klass.removeModifier(KtTokens.ABSTRACT_KEYWORD) } } } return super.visitClass(klass, data) } }) }
  61. class DataClassConverterAction : AnAction(“Data Class Converter") { override fun actionPerformed(event:

    AnActionEvent) { val project = event.project val file = event.getRequiredData(LangDataKeys.PSI_FILE) val write = WriteCommandAction.writeCommandAction(project) file.accept(object : KtTreeVisitor<Any>() { override fun visitClass(klass: KtClass, data: Any?): Void? { //... return super.visitClass(klass, data) } override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> { //do something } return super.visitNamedFunction(function, data) } }) }
  62. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    //do something } return super.visitNamedFunction(function, data) }
  63. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    val oldName = function.name ?: "" val newName = if (oldName.substring(0, 3) == "get") { oldName } else { "get${oldName.capitalize()}" } ReferencesSearch.search(function).findAll().forEach { it.handleElementRename(newName) } } return super.visitNamedFunction(function, data) }
  64. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    val oldName = function.name ?: "" val newName = if (oldName.substring(0, 3) == "get") { oldName } else { "get${oldName.capitalize()}" } val factory = KtPsiFactory(project) val param = factory.createParameter("val $oldName: ${function.typeReference?.text}") ReferencesSearch.search(function).findAll().forEach { it.handleElementRename(newName) } function.containingClass()?.getValueParameterList()?.addParameter(param) function.delete() } return super.visitNamedFunction(function, data) }
  65. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    val oldName = function.name ?: "" val newName = if (oldName.substring(0, 3) == "get") { oldName } else { "get${oldName.capitalize()}" } val factory = KtPsiFactory(project) val param = factory.createParameter("val $oldName: ${function.typeReference?.text}") ReferencesSearch.search(function).findAll().forEach { it.handleElementRename(newName) } function.containingClass()?.getValueParameterList()?.addParameter(param) function.delete() } return super.visitNamedFunction(function, data) }
  66. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    val oldName = function.name ?: "" val newName = if (oldName.substring(0, 3) == "get") { oldName } else { "get${oldName.capitalize()}" } val factory = KtPsiFactory(project) val param = factory.createParameter("val $oldName: ${function.typeReference?.text}") ReferencesSearch.search(function).findAll().forEach { when (it.element.language) { JavaLanguage.INSTANCE -> it.handleElementRename(newName) KotlinLanguage.INSTANCE -> it.element.parent.replace(factory.createExpression(param.name!!)) } } function.containingClass()?.getValueParameterList()?.addParameter(param) function.delete()
  67. override fun visitNamedFunction(function: KtNamedFunction, data: Any?): Void { write.run<Throwable> {

    val oldName = function.name ?: "" val newName = if (oldName.substring(0, 3) == "get") { oldName } else { "get${oldName.capitalize()}" } val factory = KtPsiFactory(project) val param = factory.createParameter("val $oldName: ${function.typeReference?.text}") ReferencesSearch.search(function).findAll().forEach { when (it.element.language) { JavaLanguage.INSTANCE -> it.handleElementRename(newName) KotlinLanguage.INSTANCE -> it.element.parent.replace(factory.createExpression(param.name!!)) } } function.containingClass()?.getValueParameterList()?.addParameter(param) function.delete()
  68. Some links • Source code for this example: 
 https://github.com/riclage/ConvertToDataClassPlugin

    • Intro to Intellij Plugins: 
 https://www.jetbrains.org/intellij/sdk/docs/basics.html • More examples:
 - Json to Data Class plugin: 
 https://github.com/wuseal/JsonToKotlinClass
 
 - Permissions dispatcher: 
 https://github.com/permissions-dispatcher/permissions-dispatcher-plugin
  69. Java Compatibility @Parcelize data class User(val uuid: String, val login:

    String, val name: String, val email: String, val extraInfo: String?): Parcelable { fun withExtraInfo(extraInfo: String): User { return copy(extraInfo = extraInfo) } companion object { @JvmStatic fun create(login: String, name: String, email: String) = User(UUID.randomUUID().toString(), login, name, email, null) } }
  70. Java Compatibility @Parcelize data class User(val uuid: String, val login:

    String, val name: String, val email: String, val extraInfo: String?): Parcelable { fun withExtraInfo(extraInfo: String): User { return copy(extraInfo = extraInfo) } companion object { @JvmStatic fun create(login: String, name: String, email: String) = User(UUID.randomUUID().toString(), login, name, email, null) } }
  71. The Importance of Tests • Ensure correct serialization • Ensure

    correct use of converted methods/fields • If needed, data classes can be mocked with Mockito (https://github.com/mockito/mockito/wiki/What%27s-new- in-Mockito-2#unmockable)
  72. Final result • 46 @AutoValue classes were generating 83 additional

    classes and 546 additional methods. • 4 dependencies removed (AutoValue + 3 plugins) / 1 dependency added (GsonValue) • Build time • Clean build: from ~5m42s to ~4m28s • Removing/adding a field: from ~54s to ~39s • Apk size remained roughly the same • Current app startup time with mixed serialisation approach: Up from ~1.2s to ~1.45s