The case of deserialization of a Kotlin data class

Today we faced a tricky issue when trying to deserialize a data class using Gson.

We got a report from QA of a feature “not working” for the release builds of our library. This feature is quite new, so we started the investigation by comparing it with other similar features. Tracking down the issue, it looked like the network code was not working, unable to deserialize a given object.

We use the same pattern for all of the network calls, by using Retrofit and Gson. The JSON responses are deserialized to very simple Java classes, like this one:

public class Content {
    public String locale;
    public String content;

    public String getLanguage() {
        return locale;
    }

    public void setLanguage(String language) {
        locale = language;
    }

    public String getText() {
        return content;
    }

    public void setText(String text) {
        content = text;
    }
}

But then, we realized that the new feature was developed in Kotlin, and used data classes to get the response data. Like so:

class AddressDeliveryRequest(
        val addressId: String,
        val cartId: String,
        val email: String)

So, at first sight may be the same, but… something was off.

Proguard (in fact, now R8) is used to optimize the release builds. There’s an specific rule for the package that includes all this classes:

-keepclassmembers public class com.myapp.network.data.** { public *; }

By examining this, I realized that in the Java classes, the backing fields were not private, as is the common case when you try to control the access to is by using a getter and a setter.

So, I decided to look at the generated code for the Kotlin data class to confirm my suspicion. So, from the Kotlin data class, I selected Tools -> Kotlin -> Show Kotlin Bytecode and then, in the pane that open press Decompile to see the equivalent Java code.

public final class AddressDeliveryRequest {
   @NotNull
   private final String addressId;
   @NotNull
   private final String cartId;
   @NotNull
   private final String email;

   @NotNull
   public final String getAddressId() {
      return this.addressId;
   }

   @NotNull
   public final String getCartId() {
      return this.cartId;
   }

   @NotNull
   public final String getEmail() {
      return this.email;
   }

   public AddressDeliveryRequest(@NotNull String addressId, @NotNull String cartId, @NotNull String email) {
      Intrinsics.checkParameterIsNotNull(addressId, "addressId");
      Intrinsics.checkParameterIsNotNull(cartId, "cartId");
      Intrinsics.checkParameterIsNotNull(email, "email");
      super();
      this.addressId = addressId;
      this.cartId = cartId;
      this.email = email;
   }
}

So, the fields that are expected by Gson were being removed by R8 as it didn’t match the rule that specifies that all the public fields should be kept.

So, in order to implement a quick fix, and pending a further investigation to see if we can come with a nicer solution, we changed the data class to this:

class AddressDeliveryRequest(
        @JvmField val addressId: String,
        @JvmField val cartId: String,
        @JvmField val email: String) {

    fun getAddressId() = addressId
    
    fun getCardId() = cartId
    
    fun getEmail() = email
    
}

With this, the generated code matched what was expected by R8 and Gson:

public final class AddressDeliveryRequest {
   @JvmField
   @NotNull
   public final String addressId;
   @JvmField
   @NotNull
   public final String cartId;
   @JvmField
   @NotNull
   public final String email;

   @NotNull
   public final String getAddressId() {
      return this.addressId;
   }

   @NotNull
   public final String getCardId() {
      return this.cartId;
   }

   @NotNull
   public final String getEmail() {
      return this.email;
   }

   public AddressDeliveryRequest(@NotNull String addressId, @NotNull String cartId, @NotNull String email) {
      Intrinsics.checkParameterIsNotNull(addressId, "addressId");
      Intrinsics.checkParameterIsNotNull(cartId, "cartId");
      Intrinsics.checkParameterIsNotNull(email, "email");
      super();
      this.addressId = addressId;
      this.cartId = cartId;
      this.email = email;
   }
}

Notice that there’s an open bug in Gson for already quite a long time requesting getters and setters support.

Kotlin interop: mixing Kotlin and Java ButterKnife-annotated activities

I’ve been working with Kotlin for a while, mainly for side-projects or toy-projects. Since last Google I/O 2017 announcement it has become clear that there are no more reasons or excuses to not use it in production.

One of the big selling points of Kotlin is that you can start small, by converting one class or two, or by creating new ones, while keeping all the remaining code in Java. So, interop between the two languages is almost 100% transparent. Almost.

Working to convert a small project step by step, I started to convert activities into Kotlin. Those activities use ButterKnife (I’m using current version, which is 8.7.0) to inject the views. So, after converting the first activity I stumbled upon a problem with the annotation processor: in Gradle script, you have to use either annotationProcessor or kapt, but not both at the same time. So, you have to choose:

  • using annotationProcessor only will not find Kotlin classes, and because of that injection will silently fail at runtime,
  • using kapt only will make compilation fail.

The final workaround I found was:

  • using kapt3 (by applying kotlin-kapt plugin to the Gradle script) and,
  • adding a @JvmField() annotation in addition to ButterKnife annotations so Kotlin compiler generates public fields instead of getters and setters.

By applying kapt3 we fix the compilation error involving “kotlin.jvm.internal.FunctionReference.(ILjava/lang/Object;)V” and by converting Kotlin fields to plain-old Java fields we allow ButterKnife compiler to find the fields to inject, as is unable to find Kotlin fields.

You can find the source code with different options in different branches (the one with the final solution is kotlin-workaround) in this GitHub project.

The project has two activities, one (MainActivity) that is written in Java and kept in this language, and the second one (NextActivity) that is converted to Kotlin. Notice that a simple suite of tests is available to check that both activities are being correctly injected, and that there is a TextView in both activities that has its text replaced by code to prove that the activity has been successfully injected.

Hope this tip is useful!

Kotlin RC and kotlinx

I’ve been toying with Kotlin for a while. Yesterday RC was released, making it one step closer to have a stable version.

One small change that has made me scratch my head a little, even if I read the solution, has been the changes regarding Kotlin Extensions. This had been integrated with the main Kotlin plugin (instead of being a separated plugin) but on RC you need to change your Gradle file a little to keep using it.

The symptom is that you find an “Unresolved reference: kotlinx” plus others that come from the same namespace. Resolution is easy: in the announcement for the release Kotlin 1.0 Release Candidate is Out! in the section Tooling, it reads (bold is mine):

To enable Android Extensions in Gradle in a more idiomatic way, we now say:

apply plugin: 'kotlin-android-extensions'

in the build.gradle file (individually for each project).

The old way doesn’t work any more and prints fixing instructions to the output.

So, I haven’t found this instructions, but the solution is easy: apply the new kotlin-android-extensions plugin right after the kotlin-android plugin in your gradle file. Usually, the end result will be like this:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

After that you just need to sync your Gradle configuration and rebuild the whole project.