In my last blog post I have introduced FragmentArgs an Annotation Processor for Fragments that reduces writing boilerplate code. In this post I want to talk about a similar problem android developer face: Writing boilerplate code for Parcelable
This post is part of a series of posts about useful annotation processors like FragmentArgs or AnnotatedAdapter
Parcelable is a extreme fast way to serialize objects on Android. Unfortunately the developer has to implement both the Parcelable interface
and the CREATOR
for each model class. You may ask yourself: “Does not someone else could write that code for me?”. Annotation processing can be used to generate java code and it is possible to generate Parcelable
code. There are already two good libraries that are doing this job: Parceler and AutoParcel. However I ended up by writing my own annotation processor called ParcelablePlease and I want to explain why.
Parceler
Parceler seems to be the easiest way to make classes Parcelable. All you have to do is to annotate your class with @Parcel
. However the class itself will not implement the Parcelable interface. The Parceler annotation processor creates a wrapper class for each @Parcel
annotated class and this generated class implements Parcelable and is responsible to set the values of the real model class (annotated with @Parcel) and vice versa. Hence the model class itself is not an instance of Parcelable
and you have to wrap and unwrap the real model class:
@Parcel
public class Example {
String name;
int age;
public Example(){ /*Required empty bean constructor*/ }
}
// Write as Parcelable
Bundle bundle = new Bundle();
bundle.putParcelable("example", Parcels.wrap(example));
// Read as Parcelable
Example example = Parcels.unwrap(getIntent().getExtras().get("example"))
The downside is that Parceler is not compatible with other libraries who require a Parcelable object like FragmentArgs does. That’s the reason why in the most of my apps I can’t use Parceler.
AutoParcel
AutoParcel is an annotation processor, inspired by Google’s AutoValue to generate parcelable classes. The idea of AutoValue is that you declare an abstract class with abstract methods. This abstract methods are detected by an annotation processor at compile time. Furthermore, the annotation processor creates a class that extends from this abstract class and implements the missing abstract methods (and generates the fields). Additionally AutoValue generates null-checks, toString(), hashCode(), equals() and other methods for you. Basically it does something similar like project lombok by using annotation processing instead of magic byte code manipulation (weaving). Like the name already suggests, AutoParcel does pretty the same with the goal to generate the Parcelable code for you. So a class using AutoParcel looks like this:
@AutoParcel
abstract class SomeModel implements Parcelable {
abstract String name();
abstract List<SomeSubModel> subModels();
abstract Map<String, OtherSubModel> modelsMap();
static SomeModel create(String name, List<SomeSubModel> subModels, Map<String, OtherSubModel> modelsMap) {
return new AutoParcel_SomeModel(name, subModels, modelsMap);
}
}
As you can see SomeModel
is an abstract class and the full implementation of this abstract class AutoParcel_SomeModel
is generated by the annotation processor. This seems to be a very straightforward solution since SomeModel
is a “real” Parcelable
(unlikely Parcelers approach). I have to admit that I’m a big AutoValue fan and I use it quite often in backend applications. I was really excited to see this concept for Parcelable on Android. AutoParcel supports a lot of types, generics and inheritance. However there is one big issue with this approach that makes it not useable in my apps: You can’t easily use it with other frameworks where you have to annotate fields or methods like many json parsers require. In the most of the android apps I have written so far I parse json (with jackson or gson) and I want to make this classes Parcelable to pass them trough intents or as fragment arguments. There may be some workarounds but they seem to be not very handy. For example there is an ongoing discussion of how to parse with Gson an AutoParcel.The most promising solution is to write your own gson JsonDeserializer for each AutoValue generated class to map between SomeModel
and the generated AutoValue_SomeModel
. You could automate this step by writing an additional Annotation Processor for generating TypeAdapterFactory for gson. But it isn’t a handy solution and what about other third party libraries?
Update: Since AutoValue 1.3 supports extensions AutoParcel is not needed anymore because you can directly use AutoValue with Parcelable extension and Gson extension.
ParcelablePlease
At the end I ended up by writing a small Annotation Processor called ParcelablePlease. The goal was to support the most scenarios you face in real world apps. Hence it’s not that powerful as AutoValue is (I only spend few hours on this project), but it will directly implement the Parcelable interface on the model class to avoid to run into the same problems as Parceler or AutoParcel.
Simply annotate your model class with @ParcelablePlease
:
@ParcelablePlease
public class Model implements Parcelable {
int id;
String name;
OtherModel otherModel;
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
ModelParcelablePlease.writeToParcel(this, dest, flags);
}
public static final Creator<Model> CREATOR = new Creator<Model>() {
public Model createFromParcel(Parcel source) {
Model target = new Model();
ModelParcelablePlease.readFromParcel(target, source);
return target;
}
public Model[] newArray(int size) {
return new Model[size];
}
};
}
Like you have seen above you have to write the code to connect the generated code (ModelParcelablePlease) with your Model class. I know that’s not what we want. So I was searching for a solution for not writing this boilerplate code. One option was to use compile time code weaving like you can do with afterburn. However, I was not convinced that this would be the right direction. I have a bad vibe about byte code manipulation. I don’t like this “magic” things, because it’s not easy to debug them. So at the end I came to a unspectacular but efficient solution. I wrote an intellij plugin that generates the CREATOR and Parcelable code where the Model class gets connected to the generated code (Annotation Processor). It’s some kind of hybrid solution: you only have to run the IntelliJ plugin to generate the @Parcelable Annotation, the CREATOR and writeToParcel(). The serialization code is generated by the annotation processor. Adding or removing a field does not mean you have to rerun the IntelliJ plugin because the annotation processor is responsible for serializing and deserializing all class fields.
Like mentioned before ParcelablePlease has been built with the aim to make it work in real applications. Therefore in ParcelabelPlease you can specify which fields you want to make parcelable by using @ParcelableThisPlease
and @ParcelableNoThanks
as well as setting default configurations. Another feature for edge cases is @Bagger
. With @Bagger you provide a implementation of how a field should be serialized.
For example:
public class DateBagger implements ParcelBagger<Date> {
@Override public void write(Date value, Parcel out, int flags) {
if (value == null) {
out.writeLong(-1);
} else {
out.writeLong(value.getTime());
}
}
@Override public Date read(Parcel in) {
long timeMillis = in.readLong();
if (timeMillis == -1) {
return null;
}
return new Date(timeMillis);
}
}
@ParcelablePlease
public class Person implements Parcelable {
int id;
String name;
@Bagger(DateBagger.class)
Date date;
}
Note that java.util.Date is already supported by ParcelablePlease. The example above is just to give you an idea of how a implementation of @Bagger and ParcelBagger could look like. More inforation can be found at the ParcelablePlease github site
Conclusion
Annotation Processor can reduce writing boilerplate code to make a class Parcelable. AutoParcel is a very promising approach. Unfortunately there are issues with other third party libraries. I really would love to use AutoParcel but right now (from my point of view) it’s not the best solution for the most of my apps. In the meantime you may find ParcelablePlase useful. However, I will keep an eye on you AutoParcel!
In my next post I want to talk about AnnotatedAdapter, an annotation processor for RecyclerView’s adapter.