Updated 5 days ago | GitHub

Using Parcelable

Overview

Due to Android’s memory management scheme, you will often find yourself needing to communicate with different components of your application, system components, or other applications installed on the phone. Parcelable will help you pass data between these components.

Android uses Binder to facilitate such communication in a highly optimized way. The Binder communicates with Parcels, which is a message container. The Binder marshals the Parcel to be sent, sends and receives it, and then unmarshals it on the other side to reconstruct a copy of the original Parcel.

The current recommended approach is the first-party kotlin-parcelize Gradle plugin, which generates the entire Parcelable implementation for you at compile time.
Add it to your app module’s build.gradle using the plugins { } block:

plugins {
    id 'kotlin-parcelize'
}

Or in Kotlin DSL (build.gradle.kts):

plugins {
    id("kotlin-parcelize")
}

Then annotate your class with @Parcelize and declare every property you want serialized in the primary constructor:

import kotlinx.parcelize.Parcelize

@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int): Parcelable

See the kotlin-parcelize documentation for more examples of how to use the @Parcelize annotation, including custom serialization with Parceler and skipping properties with @IgnoredOnParcel.

Legacy Java helpers

Before Kotlin became the default language for Android, two third-party tools were popular for cutting the Java Parcelable boilerplate: the Parceler annotation library (last commit to master in October 2020 — see the archived Parceler guide) and the android-parcelable-intellij-plugin code generator for IntelliJ/Android Studio (last commit in May 2016). Both are unmaintained, so avoid adding them to new projects — for Kotlin classes use kotlin-parcelize above, and for Java classes that cannot be converted, implement Parcelable manually as shown below.

Creating a Parcelable, The Manual Way (In Java)

To allow for your class instances to be sent as a Parcel you must implement the Parcelable interface along with a static field called CREATOR, which itself requires a special constructor in your class.

Defining a Parcelable Object

Here is a typical implementation:

public class MyParcelable implements Parcelable {
    // You can include parcel data types
    private int mData;
    private String mName;
    
    // We can also include child Parcelable objects. Assume MySubParcel is such a Parcelable:
    private MySubParcelable mInfo;

    // This is where you write the values you want to save to the `Parcel`.  
    // The `Parcel` class has methods defined to help you save all of your values.  
    // Note that there are only methods defined for simple values, lists, and other Parcelable objects.  
    // You may need to make several classes Parcelable to send the data you want.
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(mData);
        out.writeString(mName);
        out.writeParcelable(mInfo, flags);
    }

    // Using the `in` variable, we can retrieve the values that 
    // we originally wrote into the `Parcel`.  This constructor is usually 
    // private so that only the `CREATOR` field can access.
    private MyParcelable(Parcel in) {
        mData = in.readInt();
        mName = in.readString();
        mInfo = in.readParcelable(MySubParcelable.class.getClassLoader());
    }

    public MyParcelable() {
        // Normal actions performed by class, since this is still a normal object!
    }

    // In the vast majority of cases you can simply return 0 for this.  
    // There are cases where you need to use the constant `CONTENTS_FILE_DESCRIPTOR`
    // But this is out of scope of this tutorial
    @Override
    public int describeContents() {
        return 0;
    }

    // After implementing the `Parcelable` interface, we need to create the 
    // `Parcelable.Creator<MyParcelable> CREATOR` constant for our class; 
    // Notice how it has our class specified as its type.  
    public static final Parcelable.Creator<MyParcelable> CREATOR
            = new Parcelable.Creator<MyParcelable>() {

        // This simply calls our new constructor (typically private) and 
        // passes along the unmarshalled `Parcel`, and then returns the new object!
        @Override
        public MyParcelable createFromParcel(Parcel in) {
            return new MyParcelable(in);
        }

        // We just need to copy this and change the type to match our class.
        @Override
        public MyParcelable[] newArray(int size) {
            return new MyParcelable[size];
        }
    };
}

Note that the Parcelable interface has two methods defined: int describeContents() and void writeToParcel(Parcel dest, int flags). After implementing the Parcelable interface, we need to create the Parcelable.Creator<MyParcelable> CREATOR constant for our class which requires us to define createFromParcel, newArray.

Passing Data Between Intents

We can now pass the parcelable data between activities within an intent:

// somewhere inside an Activity
MyParcelable dataToSend = new MyParcelable();
Intent i = new Intent(this, NewActivity.class);
i.putExtra("myDataKey", dataToSend); // using the (String name, Parcelable value) overload!
startActivity(i); // dataToSend is now passed to the new Activity

and then access the data in the NewActivity that was launched using:

public class NewActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        MyParcelable object = (MyParcelable) getIntent().getParcelableExtra("myDataKey");
    }
}

Now we can access the parcelable data from within the launched activity!

Note: the single-argument getParcelableExtra(String) shown above (along with Parcel.readParcelable(ClassLoader)) was deprecated in API level 33 in favor of the type-safer getParcelableExtra(String, Class) overload — e.g. getIntent().getParcelableExtra("myDataKey", MyParcelable.class). If your app also runs on devices below API 33, use IntentCompat.getParcelableExtra(...) from AndroidX core, which calls the right variant on every API level.

Passing Data Result Back to Parent Activity

If a launched activity is returning a data result back to the parent activity, the onActivityResult() method in the parent activity is invoked:

// ActivityOne.java, time to handle the result of the sub-activity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  // REQUEST_CODE is defined above
  if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
     // Extract object from result extras
     // Make sure the key here matches the one specified in the result passed from ActivityTwo.java
     MyParcelable object = data.getParcelableExtra("myDataKey");  
  }
} 

Instead of using getIntent() to retrieve the passed object in this case, we access the Intent object via the parameter
representing the result (in this example, “data”).

What It Is Not

You may notice some similarities between Parcelable and Serializable. DO NOT, I repeat, DO NOT attempt to persist Parcel data. It is meant for high-performance transport and you could lose data by trying to persist it.

Using Parcelable compared to Serializable can achieve up to 10x performance increase in many cases for transport which is why it’s the Android preferred method.

Caveats

There are a few common gotchas associated to Parcelable to consider below:

  • One very important thing to pay close attention to is the order that you write and read your values to and from the Parcel. They need to match up in both cases. In my example, I write the int and then the String to the Parcel. Afterwards, I read them in that same exact order. The mechanism that Android uses to read the Parcel is blind and completely trusts you to get the order correct, or else you will run into run-time crashes.

  • Another problem I have encountered is with ClassNotFound exceptions. This is an issue with the Classloader not finding your class. To fix this you can manually set the Classloader to use. If nothing is set, then it will try the default Classloader which leads to the exception.

  • As mentioned before you can only put primitives, lists and arrays, Strings, and other Parcelable objects into a Parcel. This means that you cannot store framework dependent objects that are not Parcelable. For example, you could not write a Drawable to a Parcel. To work around this problem, you can instead do something like writing the resource ID of the Drawable as an integer to the Parcel. On the receiving side you can try to rebuild the Drawable using that. Remember, Parcel is supposed to be fast and lightweight! (though it is interesting to see Bitmap implementing Parcelable)

  • Parcel.writeBoolean(boolean) and Parcel.readBoolean() were added in API 29 (Android 10), so on a current minSdk you can write booleans directly: out.writeBoolean(myBoolean); and myBoolean = in.readBoolean();. If you still support older API levels, write a byte instead with out.writeByte((byte) (myBoolean ? 1 : 0)); and read it back with myBoolean = in.readByte() != 0;.

References