A comparison of Parcelable boilerplate libraries

Android developers have several choices for libraries to use for making value objects Parcelable, but how do they stack up against one another? This post aims to compare of all the available options.

Overview:

The following four libraries will be evaluated:

The comparisons will be method count, speed, built-in type support, custom type support, field exclusion strategy, and AutoValue support.

* PaperParcel is my own library. I think it's great. However this post is intended to be fair and all points are properly measured (some don't favour PaperParcel at all). Hopefully this post doesn't come off the wrong way. My goal is to outline the advantages of each library so that people can make informed decisions about which to use.

Method Count

This comparison measures the number of methods that are added to your APK just by including the library, as well as how many additional methods are generated per annotated class.

First, let's look at the absolute bare minimum number of method references for a Parcelable class:

Minimum set of methods from a custom Parcelable.Creator class:

void <init>()  
java.lang.Object createFromParcel(android.os.Parcel)  
test.MyClass createFromParcel(android.os.Parcel)  
java.lang.Object[] newArray(int)  
test.MyClass[] newArray(int)  

Minimum set of methods from a Parcelable class:

void <clinit>()  
void <init>()  
int describeContents()  
void writeToParcel(android.os.Parcel, int)  

Note: if the duplicated createFromParcel and newArray method references looks strange to you, I highly recommend checking out Jake Wharton's talk on Exploring Java's Hidden Costs.

This minimum set of method references is 9 per Parcelable class. Comparisons will exclude these methods because they are unavoidable.

Parceler

Parceler has a relatively large base method count and also generates the largest amount of methods per generated class. v1.1.6 of Parceler (the latest version at the time of writing this) includes 606 library methods and each class generates 4 additional methods per generated class:

java.lang.Object getParcel()  
test.MyClass getParcel()  
test.MyClass read(android.os.Parcel, org.parceler.IdentityCollection)  
void write(test.MyClass, android.os.Parcel, int, org.parceler.IdentityCollection)  
AutoParcel

In terms of method references, AutoParcel is pretty good. It has zero library methods and only generates two additional methods per generated class:

void <init>(android.os.Parcel)  
void <init>(android.os.Parcel, test.MyClass.AutoValue_MyClass$1)  

Note that the second method reference generated is a synthetic accessor method! This can easily be removed just by making the first constructor non-private. If the author is reading this, he may wish to fix it.

AutoValue: Parcelable Extension

When it comes to method count, this library takes the cake! No library methods and no wasted methods! Great job and design by Ryan Harter.

PaperParcel

PaperParcel is kind of like a refined Parceler. It has 190 base library methods, and generates 3 additional methods per generated class:

void <clinit>()  
void <init>()  
void writeToParcel(test.MyClass, android.os.Parcel, int)  

Speed

All of these libraries are much faster than using Serializable, so if you're using any of them then you're already a good citizen. But which is the fastest?

As a part of PaperParcel, I have written a sample app to measure this! The results are as follows (fastest to slowest):

1st. PaperParcel
2nd. AutoValue: Parcel Extension
3rd. Parceler
4th. (Not included in the sample) AutoParcel

Note: AutoParcel is slow because of the following issue: Dismantle instanceof checks. Basically AutoParcel just reads and writes using Parcel.readValue and Parcel.writeValue which has suboptimal performance.

An important note on performance is that all of these libraries support Serializable. This means that, despite all your efforts to avoid using Serializable, these libraries may still be generating calls to Parcel.readSerializable and Parcel.writeSerializable. If you're using any of these projects, it may pay to do a "find references" on those Parcel methods just to check if you're accidentally using it anywhere. PaperParcel (as of v2.0.0) has an option to disable Serializable support and hence compilation will fail if a type couldn't be written any other way.

Finally, Parceler supports reflection by default. If you're using this library, be very careful to never use private fields/constructors! If you do, it'll access them via reflection. The worst case of this is with collection field types: if you have a private collection field, it'll read the size of that collection (via reflection) on every iteration of the loop while writing it to a Parcel... Ouch! PaperParcel can also allow reflection access, but it's opt-in per field/constructor via an explicit annotation.

Built-In Type Support

Parceler

Parceler has the largest set of built in types. The full list can be found here. This large type support makes Parceler pretty easy to get started with.

AutoParcel

AutoParcel supports the types that can be handled by Parcel.readValue and Parcel.writeValue.

AutoValue: Parcel Extension

AutoValue: Parcel Extension supports only the types that are handled by the Parcel class.

PaperParcel

PaperParcel, while flexible in its type system like Parceler, tries to stay as small as possible. For this reason, it supports all of the types handled by Parcel + some common extras (e.g. java.util.Set).

Custom Type Support

There are often times where you need to use fields in your models which aren't supported by these libraries, or they're only supported by Serializable. Examples might include Set, Date, BigInteger, etc. For these cases, some of these libraries include support for custom serialization:

Parceler

Parceler includes the ParcelConverter API for this purpose. While the API provides you with some helpful base classes for collections, for everything else you still need to handle null-checking yourself manually. This can be easy to forget and error prone. Other than that, ParcelConverter looks like a good solution.

AutoParcel

I'm not aware of any way to do this in AutoParcel.

AutoValue: Parcel Extension

This library includes a TypeAdapter API. I like the approach of having an optional runtime dependency in case you don't need this functionality. Unfortunately, this API still has a lot of rough edges.

Firstly, you have to apply the type adapter to every field where it should be used. If you write a java.util.Date adapter, you and your team need to remember to use the @ParcelAdapter(DateAdapter.class) annotation on every date field.

Secondly, if you want to create an adapter to handle collections, you need to create a separate adapter for every item type you use with that collection. E.g.:

@AutoValue
public abstract class MyClass {  
  @ParcelAdapter(IntegerSetAdapter.class) Set<Integer> set1;
  @ParcelAdapter(LongSetAdapter.class) Set<Long> set2;
}

Unlike Parceler, null-checking is automatic. However, if you're doing a collection adapter, you need to remember to do null-checking for each item type.

PaperParcel

PaperParcel TypeAdapters are influenced heavily by AutoValue: Parcel Extension, only they fix the limitations of that API:

  • Collection adapters can be generic and hence be used with any item type.
  • Adapters are also be registered globally, not per field.
  • Null checking is completely automatic.

Field Exclusion Strategy:

Only PaperParcel and Parceler include field exclusion techniques.

With Parceler, you can exclude a field using a custom @Transient annotation. Alternatively, you can use the @Parcel(analyze) API to tell Parceler which superclasses to exclude.

PaperParcel takes some inspiration from Gson with its field exclusion strategies. You can:

a) Exclude fields using the transient modifier.
b) Exclude fields using custom annotations.
c) Exclude non-exposed fields (using an expose annotation)
d) You can even customize which modifiers (or combination of modifiers) can be used to exclude fields.

AutoValue Support

AutoParcel and AutoValue: Parcel Extension both support AutoValue via an AutoValue extension, which is pretty obvious. You might be surprised to find out that PaperParcel does too (and always has). The only difference is that PaperParcel can support AutoValue as well as non-AutoValue classes at the same time. This is great for teams that are transitioning over to AutoValue.

Parceler can support AutoValue classes too via its Static factory support, but using an AutoValue extension is the offical way. It's also a lot easier API-wise.

Conclusion

If you only use AutoValue, and don't care for the advantages PaperParcel offers, then AutoValue: Parcel Extension is a fine choice. It has great developers behind it and is the most efficient in terms of method count.

Unfortunately I no longer see any reason to use AutoParcel over its competition.

If you are using Parceler, you might find PaperParcel interesting. I'm not suggesting you switch over, but I'm hoping that this post will convince you that there's another good option out there.

Parceler benefits over PaperParcel include:

  • Great type support out-of-the-box.
  • It has been around longer, hence it's very mature.
  • Less boilerplate.
  • Edit: Reddit user la__bruja pointed out that Parceler also has an API to create Parcelable wrappers for classes found in non-Android modules.

PaperParcel benefits over Parceler include:

  • Fewer method references.
  • It's faster.
  • It does not use Parcelable wrapper classes, which sometimes makes library compatibility difficult. It also means you don't need to do manual wrapping and unwrapping in your UI as the parcelling code is all encapsulated in your model objects.
  • The API is much more refined, yet still maintains a more flexible field exclusion strategy.
  • Generated code handles null-checks for you when defining custom serialization.
  • Allows for easy transition to using Kotlin or AutoValue.
  • No runtime reflection and doesn't require careful coding to avoid using private fields or constructors.
  • Allows for disabling Serializable support completely if you wish to ensure Parcel.readSerializable and Parcel.writeSerializable are never used.
  • The generated code is actually readable (minor point but it is important to me).

Thanks for reading!

Please comment below if you think there should be some other comparison in the blog post and I'll update it.

Brad Campbell

Android application developer, currently working as the lead on the ANZ goMoney NZ applications. All of the opinions and code on this blog are my own, and not that of ANZ.