Tuesday, September 11, 2012

A Parcelable Tutorial for Android

Parcelable Interface Overview

In one of my earlier posts, I mentioned writing an article about FOSOAuthBundle integration with an Android client. To keep that article to the point, I need to explain some concepts beforehand. One of the important concepts is the Android Parcelable interface that allows data to be transferred between different processes/threads. Certain network operations with Android such as authentication with OAuth2 and then fetching data from a REST endpoint should be performed in the background in order not to block the UI thread. This requires data to be fetched by a service (I have opted for Intent Services in my implementation) in the background and then passed back to the calling activity/fragment with a result callback. This is where the Parcelable interface comes into play.

Basically, the Parcelable interface allows your classes to be flattened inside a message container called a Parcel to facilitate high performance inter process communication. The received parcel data can then be unflattened to generate object/entity instances.

A Basic Parcelable Example

A Parcelable implementation is pretty straight forward. Override the necessary methods called writeToParcel() and describeContents(), add a static field called CREATOR which generates instances of your Parcelable class from a Parcel, and overload the class constructor which expects a Parcel as a parameter and calls the readFromParcel() utility method. Here is a basic example:

public class Conversation implements Parcelable {

    // ...
    protected String lastComment;
    protected Integer messageCount;
    protected Date createdAt;
    // ...

    // ...
    public Conversation(Parcel in) {
     readFromParcel(in);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        // ...
        out.writeString(lastComment);
        out.writeInt(messageCount);
 out.writeSerializable(createdAt);
        // ...
    }

    private void readFromParcel(Parcel in) {  
        // ...
        lastComment = in.readString();
 messageCount = in.readInt();
 createdAt = (Date) in.readSerializable();
        // ...
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
  
        public Conversation createFromParcel(Parcel in) {
            return new Conversation(in);
        }
 
        public Conversation[] newArray(int size) {
            return new Conversation[size];
        }
        
    };

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

}

Nested Parcelable Classes

Lets say we have a Conversation class instance with an embedded User class instance that holds some information about a user that has initiated the conversation. Basically, we are talking about an one-to-one embedded mapping. In this case, if the fromUser property holds a User instance, then writing the user data to parcel would be accomplished as shown below:

@Override
public void writeToParcel(Parcel out, int flags) {
    // ...
    out.writeParcelable(fromUser, flags);
    // ...
}

Reading the user data from parcel:

private void readFromParcel(Parcel in) {  
    // ...
    fromUser = in.readParcelable(User.class.getClassLoader());
    // ...
}

If you have a one-to-many embedded relationship such as a list of messages in a conversation, then the syntax would change as follows:

@Override
public void writeToParcel(Parcel out, int flags) {
    // ...
    out.writeList(messages);
    // ...
}

Reading from the parcel:

private void readFromParcel(Parcel in) {  
    // ...
    in.readList(messages, Message.class.getClassLoader());
    // ...
}

Boolean Types

Android API does not have a method to write a single boolean value to a parcel. In this case, you can utilize the writeInt() method as shown below:

@Override
public void writeToParcel(Parcel out, int flags) {
    // ...
    out.writeInt(booleanValue ? 1 : 0);
    // ...
}

Reading from the parcel:

private void readFromParcel(Parcel in) {  
    // ...
    booleanValue = in.readInt() == 1;
    // ...
}

Enum Types

To flatten an Enum type in a parcel, simply implement the Parcelable interface for the Enum type. Here is an example:

public enum Status implements Parcelable {
  
    STARTED, PAUSED, FINISHED;
  
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
   
        public Status createFromParcel(Parcel in) {
            return Status.values()[in.readInt()];
 }
 
        public Status[] newArray(int size) {
     return new Status[size];
 }
         
    };

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
 out.writeInt(ordinal());
    }

}

When writing to the parcel, treat it as a nested Parcelable class:

@Override
public void writeToParcel(Parcel out, int flags) {
    // ...
    out.writeParcelable(status, flags);
    // ...
}

Reading from the parcel:

private void readFromParcel(Parcel in) {  
    // ...
    status = in.readParcelable(Status.class.getClassLoader());
    // ...
}

4 comments:

  1. I took this one step further.
    Instead of using a public constructor I just used a private method.

    Any thoughts on this?

    My code looks like this:

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
    public myClass createFromParcel(Parcel in) {
    return new myClass().readFromParcel(in);
    }

    public myClass[] newArray(int size) {
    return new myClass[size];
    }
    };

    ReplyDelete
  2. Hi nice article,

    Can you please provide an emaple on how to use the parceble inside the same Activity?

    Thanks in advance!

    ReplyDelete
  3. Thanks, this helped :) A lot of boilerplate... But apparently it's worth it: http://www.developerphil.com/parcelable-vs-serializable/

    ReplyDelete
  4. Thanks for taking the time to discuss this, I feel strongly that love and read more on this topic. If possible, such as gain knowledge, would you mind updating your blog with additional information? It is very useful for me. UC News

    ReplyDelete