Reducing Parcelable boilerplate code using Kotlin

Christophe Beyls
8 min readJul 15, 2017

--

Update (november 2019): I published a detailed new article about how to use Parcelize to have your Parcelable boilerplate code generated automatically by a Kotlin plugin. This is the preferred method to implement Parcelable in Kotlin today, even if there are still exceptional cases for which you want to write this code manually in Kotlin. The following article will help you for those cases.

Implementing the Parcelable interface and maintaining this code remains a painful day-to-day task for Android developers. Surely, Android Studio can now automatically generate most of the boilerplate code, but this doesn’t work for all member types and most importantly, the result is quite verbose and clutters classes, decreasing their readability.

About Parcelable libraries

Annotation processor libraries like PaperParcel are able to automatically generate Parcelable implementations and get rid of some of the boilerplate code. This is fine, but I don’t use these solutions because I like to have full control over the serialization process. These libraries come with their limitations. For example:

  • Many of these libraries serialize types like Enum<T> or BigDecimal using Parcel.writeSerializable() which is not the fastest implementation nor most compact representation. Some provide support for non-Parcelable types through custom adapters, but you still need to specify the adapter class in every place where you want to use it.
  • These libraries usually serialize Parcelable members inside Parcelable classes using Parcel.writeParcelable() which is less efficient to deserialize than using Parcel.writeTypedObject() when the concrete type is known.
    This is because Parcel.readParcelable() first reads the full class name in order to retrieve the corresponding Parcelable.Creator via reflection, while Parcelable.readTypedObject() uses the provided Parcelable.Creator directly.
  • In some cases, I want the Parcelable.Creator to act like a factory method determining which concrete sub-type will be created. Again, this is more efficient than using Parcel.readParcelable(). I can’t do this if the Parcelable.Creator is generated automatically.
  • I like child classes to reuse Parcelable code from their parent to avoid redundancy. This is not what most libraries do.
  • Finally, almost all libraries slightly increase the methods count of your APK file, because they bundle their own small runtime into your app and generate a few extra methods for each Parcelable class.

If you want to learn more about Parcelable libraries and their differences, I recommend this excellent article from Brad Campbell.

Kotlin for Parcelables

Simply converting classes to the Kotlin programming language without any additional trick already makes them more readable than their Java equivalent.

Consider the following example class written in Kotlin.

class Person(val name: String, val age: Int)

By using the built-in Android Studio contextual action to generate a Parcelable implementation automatically, we get the following code:

class Person(val name: String, val age: Int) : Parcelable {

constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readInt())

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(age)
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}

The result is 25 lines long.

Cleaning up the generated code

While the code quality is decent, you’ll notice that the CREATOR static field has been implemented as the companion object of that class. This is technically working, but since a class is only allowed to have a single companion object, it means you’ll have to add your class custom fields (like constants) directly into that CREATOR object:

companion object CREATOR : Parcelable.Creator<Person> {
val DEFAULT = Person("James", 40)

override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}

In addition, the Creator methods will be exposed as members of the Person class, polluting its API. This definitely doesn’t feel right and harms readability, so you’ll probably want to change the code to the recommended pattern which is to move the CREATOR field inside the companion object, and annotate it with @JvmField so it will be compiled to a static Java field:

companion object {
@JvmField val CREATOR = object : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}

We can also make the code a bit shorter by using single-expression functions.

Finally, I like to use named arguments for more clarity when calling the primary constructor from the Parcel secondary constructor and make this constructor private or protected since only the CREATOR and child classes will invoke it.

We end up with the class looking like this:

class Person(val name: String, val age: Int) : Parcelable {

private constructor(p: Parcel) : this(
name = p.readString(),
age = p.readInt())

override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(name)
dest.writeInt(age)
}

override fun describeContents() = 0

companion object {
@JvmField val CREATOR = object : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel) = Person(parcel)

override fun newArray(size: Int) = arrayOfNulls<Person>(size)
}
}
}

It’s now 21 lines long.

1. A better interface

Like Java 8, Kotlin allows to add default implementations to interfaces. We can create an interface using this feature to provide the default implementation of the describeContents() method, which in 99.99% of cases should simply return 0 (and the cases where it should return another value are badly documented anyway). Now we won’t have to write that useless method ever again.

We’ll also override the signature of the writeToParcel() method to force its Parcel argument to be non-null, because it’s actually never null and you shouldn’t bother using the safe call operator on it.

interface KParcelable : Parcelable {
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int)
}

From now on, we’ll simply make our objects implement KParcelable instead of Parcelable.

2. A creator for Creators

It’s time to leverage one of the most powerful features of the Kotlin language: higher order inline functions, which can be used like macros for code generation. In this case, we will write a function that generates implementations of these Parcelable.Creator objects that always look the same.

inline fun <reified T> parcelableCreator(
crossinline create: (Parcel) -> T) =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel) = create(source)
override fun newArray(size: Int) = arrayOfNulls<T>(size)
}

This is a modified version of the function provided by Juan Ignacio Saravia in his instructive article about Parcelables in Kotlin.

Thanks to reified generic type parameters, the arrayOfNulls() function is able to create an array of the proper concrete type at compile time. This would not have been possible to achieve in Java without using reflection at runtime.

parcelableCreator() takes a single argument: a function returning a new instance of the target class from a non-null Parcel. This argument can be a lambda, but it can also simply be a reference to a function like the class secondary constructor which takes a Parcelable as argument:

companion object {
@JvmField val CREATOR = parcelableCreator(::Person)
}

In addition, we can write a similar function to generate instances of Parcelable.ClassLoaderCreator for cases where we need access to the ClassLoader.

inline fun <reified T> parcelableClassLoaderCreator(
crossinline create: (Parcel, ClassLoader) -> T) =
object : Parcelable.ClassLoaderCreator<T> {
override fun createFromParcel(source: Parcel, loader: ClassLoader) = create(source, loader)

override fun createFromParcel(source: Parcel) = createFromParcel(source, T::class.java.classLoader)

override fun newArray(size: Int) = arrayOfNulls<T>(size)
}

Our final class has now been reduced to just 15 lines of code and is way more readable:

class Person(val name: String, val age: Int) : KParcelable {

private constructor(p: Parcel) : this(
name = p.readString(),
age = p.readInt())

override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(name)
dest.writeInt(age)
}

companion object {
@JvmField val CREATOR = parcelableCreator(::Person)
}
}

3. Extending Parcel

Parcel only supports a handful of types to serialize. What if we could extend it to support more types, so that writing an Enum to a Parcel would be as simple and as efficient as writing a String ? That’s exactly what Kotlin extension functions are made for.

inline fun <reified T : Enum<T>> Parcel.readEnum() =
readString()?.let { enumValueOf<T>(it) }

inline fun
<T : Enum<T>> Parcel.writeEnum(value: T?) =
writeString(value?.name)

We can now write an Enum to a Parcel using this syntax:

dest.writeEnum(gender)

Note how these extension functions use nullable types, so they can be used with both nullable and non-null class members. The only compromise of this solution is that it forces you to use the !! operator when reading back a non-null value to a non-null member:

gender = p.readEnum<Gender>()!!

This is safe since you know that the value written to the Parcel was non-null in the first place. If somehow you get a null value here, it means that you made a huge mistake in your serializing code and the NullPointerException is deserved.

Another pair of useful Parcel methods I mentioned earlier is Parcel.readTypedObject() and Parcel.writeTypedObject() that should be preferred for reading or writing Parcelable members when their concrete type is known. These methods are only available since API 23 and not yet part of the support library, but their implementation is straightforward. We can create compatibility versions working on any Android release using simple Kotlin extension functions:

fun <T : Parcelable> Parcel.readTypedObjectCompat(c: Parcelable.Creator<T>) =
readNullable { c.createFromParcel(this) }

fun
<T : Parcelable> Parcel.writeTypedObjectCompat(value: T?, flags: Int) =
writeNullable(value) { it.writeToParcel(this, flags) }

Note that I extracted the null handling parts of the code to external inline functions readNullable() and writeNullable() so they can be reused by other utility functions.

inline fun <T> Parcel.readNullable(reader: () -> T) =
if (readInt() != 0) reader() else null

inline fun
<T> Parcel.writeNullable(value: T?, writer: (T) -> Unit) {
if (value != null) {
writeInt(1)
writer(value)
} else {
writeInt(0)
}
}

You can find a complete set of useful extension functions for Parcel in the full source code listing at the end of this article.

As a last example, these function calls can be grouped with apply(), run() or with() to reduce the verbosity to a minimum:

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(name)
writeInt(age)
writeEnum(gender)
writeDate(birthDate)
writeTypedObjectCompat(address, flags)
}

Full source code of the Parcelable utilities

The Kotlin language itself is a powerful tool to reduce boilerplate code in Parcelable classes. For those who want full control over the serialization process with minimum overhead while keeping the code as readable as possible, this set of Kotlin utilities and best practices is a good alternative to Parcelable libraries.

As always, please share if you like. Thank you for reading!

--

--

Christophe Beyls

Android developer from Belgium, blogging about advanced programming topics.