Pondering Parcelable

In the LifecycleList sample app from the previous chapter, our EventViewModel held onto two pieces of data:

If our process is terminated, but the user returns to us with 30 minutes, ideally we would still have this data. But our EventViewModel is gone and will be replaced by a brand-new instance in the brand-new process. We could store this data in a file or database, but unless the list of Event objects is really long, we could put this data into the saved instance state Bundle.

Except for one problem: Bundle does not know anything about Event.

In order to be able to put our Event objects into the Bundle, we need to make Event be Parcelable.

Parcelable is a marker interface, reminiscent of Java’s Serializable, that shows up in many places in the Android SDK. A Parcelable object is one that can be placed into a Parcel. A Parcel is the primary vehicle for passing data between processes in Android’s inter-process communication (IPC) framework. And, of note, Bundle supports Parcelable objects.

You have two major approaches for adding Parcelable capabilities to your classes in Android:

  1. Use an annotation processor that will add in the appropriate bits of magic for you
  2. Just do it yourself

Parcelable by Annotation

Annotation processors take @Things @That @LookLike @These("snippets") and augment your code. In particular, they can generate code for you to save you typing in a bunch of boilerplate.

Kotlin users have easy access to a @Parcelize annotation that can make simple classes — such as Event — be Parcelable.

To add Parcelable support to a Kotlin class, just:

package com.commonsware.jetpack.sampler.state

import android.os.Parcelable
import android.os.SystemClock
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Event(
  val message: String,
  val activityHash: Int,
  val viewmodelHash: Int,
  val timestamp: Long = SystemClock.elapsedRealtime()
) : Parcelable

Here we have a revised version of the Event class that adds the @Parcelize annotation and the Parcelable interface. The actual code to support the Parcelable interface will be code-generated, and so we can skip it.

Support for this annotation is supplied by the kotlin-parcelize Gradle plugin, which you will see at the top of many projects, including all of the Kotlin projects in this book:

apply plugin: 'kotlin-parcelize'

You may see references to a kotlin-android-extensions plugin instead. This used to supply @Parcelize, but that plugin has been deprecated and replaced by kotlin-parcelize.

In addition, there are many third-party libraries that support Parcelable, including many annotation processors for Java that can accomplish a similar thing to what we get with Kotlin.

Parcelable by Hand

Adding Parcelable support yourself is not especially difficult, though it is a bit tedious.

The Parcelable Interface

The first steps is to add the Parcelable interface to the class. Immediately, your IDE should start complaining that you need to implement two methods to satisfy the Parcelable interface.

The easier of the two methods is describeContents(), where you will return 0, most likely.

The other method you will need to implement is writeToParcel(). You are passed in two parameters: a very important Parcel, and a usually-ignored int named flags.

Your job, in writeToParcel(), is to call a series of write...() methods on the Parcel to write out all data members of this object that should be considered part of the object as it is passed across process boundaries. There are dozens of type-safe methods for writing data into the Parcel, including:

In the case of the generated Event code shown earlier in this chapter, writeToParcel() writes out each of our four fields:

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeLong(timestamp);
    dest.writeString(message);
    dest.writeInt(activityHash);
    dest.writeInt(viewmodelHash);
  }

The CREATOR

When Android tries reading objects in from a Parcel, and it encounters an instance of your Parcelable class, it will retrieve a static CREATOR object that must be defined on that class. The CREATOR is an instance of Parcelable.Creator, using generics to tie it to the type of your class:

  @SuppressWarnings("unused")
  public static final Parcelable.Creator<Event> CREATOR = new Parcelable.Creator<Event>() {
    @Override
    public Event createFromParcel(Parcel in) {
      return new Event(in);
    }

    @Override
    public Event[] newArray(int size) {
      return new Event[size];
    }
  };

The @SuppressWarnings("unused") annotation is because the IDE will think that this CREATOR instance is not referred to anywhere. That is because it will only be used via Java reflection.

The CREATOR will need two methods. createFromParcel(), given a Parcel, needs to return an instance of your class populated from that Parcel. newArray(), given a size, needs to return a type-safe array of your class.

The typical implementation of createFromParcel() will delegate the actual work to a protected or private constructor on your class that takes the Parcel as input:

  protected Event(Parcel in) {
    timestamp = in.readLong();
    message = in.readString();
    activityHash = in.readInt();
    viewmodelHash = in.readInt();
  }

You need to read in the same values that you wrote out to the Parcel, and in the same order.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.