In our attempt to slowly migrate our codebase to Kotlin (we are at 40% at the time of this article), one of the biggest roadblocks we got was migrating our data model.
This data model was composed by roughly 30 Java classes using AutoValue and some AutoValue extensions like auto-value-parcel or auto-value-with.
Kotlin data class seemed like a good choice: Built in the Kotlin language, so we could get rid of one 3rd party dependency and we would enjoy the use of named arguments, which replaces the need of a builder, the possibility to have default arguments and the lack of having to implement toString, copy, equals, etc.
This is what a class with AutoValue looks like:
And this is the equivalent with Kotlin data class:
Dealing with Parcelable
This was the first roadblock for a smooth migration. Since we used auto-value-parcel for the Parcelable implementation, I had to look for an alternative. After checking alternatives PaperParcel was the winner with its built in Kotlin support.
The “only” issue we had, is that kotlin-kapt (a plugin for annotation processing on Kotlin) conflicted with the Java AutoValue annotated classes. I could have tried to fix that or just go ahead and migrate all the AutoValue classes at once. I decided for the latter.
So I spend a couple of days migrating all the AutoValue classes to Kotlin before I could even build the project and run a single Unit Test.
The second big issue was how the builders were used in our code. On Kotlin classes it was easy to replace the AutoValue builder with calls to the constructor with named parameters, but on Java classes it was a horror.
In Java we had to call to the constructor of the data class passing all parameters (including the ones with default values).
This is an example of what I mean:
val value = MyValue(42)
// alternatively with named params
val value = MyValue(id = 42, title = "Title")
// Java to Kotlin data class
MyValue value = new MyValue(42, "", 0, false);
// Java to AutoValue builder
MyValue value = MyValue.builder().setId(42).build();
(the problem just goes bigger the more properties you have)
The solution, in some cases, has been migrating the calling class to Kotlin as well. I did this for most of the Unit Tests.
But on other cases I didn’t want to spend time migrating the Java code to Kotlin so I had to go with the ugly big constructor call.
Accessing to the data class properties
This worked flawlessly. Because the data class properties are exposed like getters to Java, there was no need to change any of the Java code!
// On Java, this works just as before the refactor
int id = myValue.getId();
// On Kotlin, this works just as before the refactor
val id = myValue.id
“copy” instead of “with”
In AutoValue there’s a neat plugin called auto-value-with, which allows you do the following:
MyValue newValue = oldValue.withTitle("New Title");
The alternative in Kotlin also comes out-of-the-box!
val newValue = oldValue.copy(title = "New Title")
No need to use a library and no need to declare the withTitle method.
Just be aware: this is only possible when calling from Kotlin.
…and they lived happily ever after
The work was done, took less than expected, and helped us migrate a big chunk of our codebase to Kotlin. This refactor changed 250 files. 😨
Thankfully we had a good safety net of Unit and Espresso tests.
Thanks to this change, one of our project modules is now 100% Kotlin, we got rid of some 3rd party dependencies, although we had to add a new one.
We are also now enjoying null safety checking on compilation time rather than on run time.
I thought I would miss the builder pattern, but that was just on Java classes. Using our new data model from Kotlin is a breeze.
- Kotlin data classes provide everything you need to replace AutoValue
- Parcelables can be easily implemented using PaperParcel
- The biggest problem was be the lack of builder pattern when calling from Java
- Accessors to the properties won’t need to be changed
- The conflict of kotlin-kapt with AutoValue obliged me to migrate all classes at once, proceed with care!
- The copy data class method is a great replacement to auto-value-with