Pondering Parcelable
In the LifecycleList
sample app from the previous chapter, our EventViewModel
held onto two pieces of data:
- The time when the
EventViewModel
was created, and - The list of
Event
objects representing the lifecycle events
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:
- Use an annotation processor that will add in the appropriate bits of magic for you
- 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:
- Have that class declare that it implements the
Parcelable
interface, though you do not need to override any of its methods, and - Add the
@Parcelize
annotation to the class:
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:
- methods that write individual primitives (e.g.,
writeInt()
) or Java arrays of primitives (e.g.,writeStringArray()
) -
writeBundle()
, for writing out aBundle
-
writeParcelable()
andwriteParcelableArray()
, for writing out other objects that implementParcelable
- various specialized methods for particular data types (e.g.,
writeSizeF()
) or interfaces (e.g.,writeSerializable()
)
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.