Parcelable – sending arbitrary objects using Intent in Android

Using Parcelable

Parcelable is an interface for serialization/deserialization of Java objects used in Android. Parcelable object can be sent in Intent’s extras same as any other base type. Parcelable serialization/deserialization works faster than Java’s Serializable, but is a bit more complicated to implement. Nevertheless, you still need two functions to be defined and one static class with two simple methods. Not a big price for speed…

Let’s start from an example implementation. We have a simple class containing two member fields:

public class CustomData implements Parcelable
{
    private final String mId;
    private final String mText;

    public CustomData(String id, String text) {
        mId = id;
        mText = text;
    }

    @Override
    public String toString() {
        return "CustomData [mId=" + mId + ", mText=" + mText + "]";
    }

    @Override
    public boolean equals(Object o) // (1)
   {
        if( o instanceof CustomData )
       {
            CustomData d = (CustomData)o;
            return d.getId().equals( getId() ) &&
                   d.getText().equals( getText() );
        }
       else
       {
            return false;
        }
    }

    /*************************************/
    /********** Getters/setters **********/
    /*************************************/

    public String getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

    /******************************************/
    /********** Parcelable interface **********/
    /******************************************/

    @Override
    public int describeContents() { // (2)
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) // (3)
    {
        out.writeString(mId);
        out.writeString(mText);
    }

    private static CustomData readFromParcel(Parcel in) { // (4)
        String id = in.readString();
        String text = in.readString();
        return new CustomData(id, text);
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() // (5)
   {
        public CustomData createFromParcel(Parcel in) // (6)
       {
            return readFromParcel(in);
        }

        public CustomData[] newArray(int size) { // (7)
            return new CustomData[size];
        }
    };
}

Let’s go through the class code:

  1. equals(Object o) – not really needed by Parcelable, but we’ll use this method to compare objects in our test case later.
  2. describeContents() – Parcelable’s method; for most use cases it can be left as it is, ie. returning 0.
  3. writeToParcel(Parcel out, int flags) – Parcelable’s method; here we perform serialization by writing all member fields as basic data types to out object. Second argument (flags) can be ignored for most cases.
  4. readFromParcel(Parcel in) – just a helper; it builds CustomData object using data contained in Parcel. Some prefer too use a private constructor, like private CustomData(Parcel in) {…} instead of helper. The key thing is, you have to read all fields in the same order you’ve wrote them into the Parcel in writeToParcel() method. This is the only error-prone part of the process.
  5. CREATOR is a special object used to deserialize our object from Parcel. All Parceable classes must provide this object and it must be declared as public static final.
  6. Actual deserialization is done by calling this method. My implementation uses helper method readFromParcel(), some will prefer to use private constructor, others will deserialize object in place without the helper – whichever you like.
  7. Create an array of Parcelable objects when we deserialize arrays.

Once Parcelable is implemented, we can send it using Intents like any other base type (inside Bundle):

CustomData data = new CustomData("id", "text");
Intent i = new Intent();
i.putExtra("key", data);

Object deserialization is also dead simple:

Intent i = ...; // Intent containing  Bundle with key "key" and our CustomData object;
CustomData data = i.getExtras().getParcelable("key");

Test cases

Serialization code, contained in writeToParcel(Parcel out, int flags) method, and deserialization code – in our example: readFromParcel(Parcel in) – must be kept in sync. It sounds simple, but in reality things get more complicated as your objects grow new members, especially when you create some complex types needing more logic (as excersise, try to serialize Map object using Parcel – you’ll immediately get an idea). To make life simpler I advice everybody to write test cases for every Parcelable object, even if the initial implementation is trivial. This will greatly help maintaining and developing Parcelable objects. What seems to be a waste of time in practice have important consequence: any developer or maintainer will be MUCH LESS reluctant to avoid his changes to be tested if provided with bootstrapped test case, just to be filled or modified to match new implementation. Chance that a test case will be created later falls exponentially with object’s size and complication.

This is a simple test case tamplate that can be used without much hassle:

We test two elements:

  1. Test of boolean equal(Object o): we must compare objects later, so having this method working right is a key prerequisite for the next test.
  2. Serialization/deserialization: test critical parts of Parcelable by writing object to Parcel, creating it’s copy and comparing them using equal().

Having this small template around should save anybody working with Parcelable objects tons of time during development.

Template for Eclipse

Writing this repetable block of code can by annoying, so here’s a small template to make our life easier. Just put it in Window -> Preferences -> Java -> Editor -> Templates under any name you want – I prefer “parcelable”. Then just type parcelable and press Ctrl+Space to get a preliminary implementation of Parcelable interface, modify writeToParcel(), readFromParcel() and you’re set.

  • Hector Perez

    Hey this looks nice. I was actually thinking if it was possible to pass a WHOLE object as the value… I tried but it’s giving me issues.

    Similarly when reading it back from the parcel…

    Thanks!