postman
Postman is a java library that uses code generation to handle the details of implementing the Parcelable interface on Android.
The Problem
Postman is intended to be used in cases where you have parcelable classes with a lot of fields that need to be serialized. At Workday, we had a lot of classes like the following to represent the responses from server.
public class MyParcelable implements Parcelable {
int myInt;
String myString;
MyChildParcelable myChildParcelable;
ArrayList<MyChildParcelable> myParcelableList;
ArrayList<String> myStringList;
public static final Creator<MyParcelable> CREATOR = new Creator<MyParcelable>() {
@Override
public MyParcelable createFromParcel(Parcel source) {
MyParcelable myParcelable = new MyParcelable();
Bundle bundle = source.readBundle();
myParcelable.myInt = bundle.getInt("myInt");
myParcelable.myString = bundle.getString("myString");
myParcelable.myChildParcelable = bundle.getParcelable("myChildData");
myParcelable.myParcelableList = bundle.getParcelableArrayList("myParcelableList");
myParcelable.myStringList = bundle.getStringArrayList("myStringList");
return myParcelable;
}
@Override
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
}
;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Bundle bundle = new Bundle();
bundle.putInt("myInt", myInt);
bundle.putString("myString", myString);
bundle.putParcelable("myChildData", myChildParcelable);
bundle.putParcelableArrayList("myParcelableList", myParcelableList);
bundle.putStringArrayList("myStringList", myStringList);
dest.writeBundle(bundle);
}
}
If you have to write several classes like this, it gets very tedious and tiresome very quickly. And if you're like most software developers, you want to avoid tedious and tiresome like the plague.
The Solution
There is nothing fancy or unexpected in making a class parcelable. There is a lot of boiler plate and it all follows the same pattern. We could write a program that writes this code for us. So we did.
We wrote an annotation processor that can read through a class that you mark as a candidate for processing, and it will generate all the code in the Creator
and in writeToParcel(Parcel, int)
. The same class from above now becomes this:
@Parceled public class MyParcelable implements Parcelable {
int myInt;
String myString;
MyChildParcelable myChildParcelable;
ArrayList<MyChildParcelable> myParcelableList;
ArrayList<String> myStringList;
public static final Creator<MyParcelable> CREATOR = Postman.getCreator(MyParcelable.class);
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Postman.writeToParcel(this, dest);
}
}
That's all you need to do get Postman working!
How to Use Postman
Add the following lines to your build.gradle
file, replacing $postman_version
with latest version from Maven Central.
repositories {
mavenCentral()
}
dependencies {
compile "com.workday:postman:$postman_version"
compile "com.workday:postman-processor:$postman_version"
}
Note that if you use the android-apt plugin or the kotlin-android plugin, you may use apt
or kapt
respectively instead of compile
for postman-processor
, e.g.
apt "com.workday:postman-processor:$postman_version"
Postman Idioms
The previous example demonstrates the most common way you will use Postman: annotate the class in question with @Parceled
. Postman will pick up all member fields in the class and write them to or read them from the Parcel when Postman.writeToParcel(Object, Parcel)
or Postman.readFromParcel(Class, Parcel)
is called.
Unfortunately, there is still some boiler plate code you have to deal with. In your parcelable class, you must include the following lines:
public static final Creator<MyParcelable> CREATOR = Postman.getCreator(MyParcelable.class);
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
Postman.writeToParcel(this, dest);
}
replacing MyParcelable with your own class name. We hope you agree though that this is much less painful than writing the full implementations yourself.
However, to make this part easier, we have included a live template that you can use in Android Studio. Include AndroidParcelable.xml in the following directory depending on your operating system.
- Windows:
<your home directory>\.AndroidStudio<version number>\config\templates
- Linux:
~/.AndroidStudio<version number>/config/templates
- OS X:
~/Library/Preferences/AndroidStudio<version number>/templates
Restart Android Studio. Then in your Parcelable class, type postman
on an empty line and hit enter. Android Studio will write the block for you (sans imports).
What if there's a field I don't want to include in the Parcel?
In that case, annotate the offending field with @NotParceled
.
What if I don't want to include most of the fields in the Parcel?
No problem. In that case, instead of annotating the class with @Parceled
, annotate the fields you want included with @Parceled
. All other fields will be ignored.
Things to Watch Out For
- Postman cannot access private fields. Any field you want to include in the Parcel must be either package-private (aka default) or public. If you annotate a private field with
@Parceled
, Postman will generate a compilation error. A private field in a class annotated with@Parceled
will generate a compiler warning. - Postman cannot populate final fields. If you annotate a final field with
@Parceled
, Postman will generate a compilation error. A final field in a class annotated with@Parceled
will generate a compiler warning. - Postman requires a public or package-private, no arg constructor in order to instantiate the class. A class marked with
@Parceled
that does not have default constructor will generate a compilation error.
Types Postman Can Handle
Postman can write to the Parcel any of the following types.
- All primitives (e.g.
int
,boolean
) as well as their boxed counterparts (e.g. Integer, Boolean). - BigDecimal and BigInteger
- Any class that implements Parcelable.
- String and CharSequence
- Any class that implements Serializable
- The following Collections, so long as they only contain items enumerated in this list (this includes
List<List<String>>
) - The following Maps so long as they only contain keys and values enumerated in this list (this include
Map<String, List<String>>
)
Note that Postman will compile if it encounters a List<Object>
or a Map<String, Object>
, but if one of the items is not of a type that Postman can handle, it will throw a runtime exception when it tries to write the object to the parcel.
Working with Proguard
If you're using ProGuard, you must add the following line to your ProGuard rules
-keep class * implements com.workday.postman.parceler.Parceler -dontwarn com.workday.postman.codegen.*
otherwise your app will blow up.
Postman also doesn't work with obfuscation, so you if you have obfuscation turned on, you'll need to keep all of your parceled classes.