Overview

PaperParcel is an annotation processor that automatically generates the CREATOR and writeToParcel(...) implementations for you when writing Parcelable objects. PaperParcel fully supports both Java and Kotlin (including Kotlin Data Classes). Additionally, PaperParcel supports Google's AutoValue via an AutoValue Extension.

See this blog entry for a comparison with alternative solutions.

Usage

Using PaperParcel is easy, the API is extemely minimal. Let's look at an example:

@PaperParcel // (1)
public final class User implements Parcelable {
  public static final Creator<User> CREATOR = PaperParcelUser.CREATOR; // (2)

  long id; // (3)
  String firstName; // (3)
  String lastName; // (3)

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

  @Override public void writeToParcel(Parcel dest, int flags) {
    PaperParcelUser.writeToParcel(this, dest, flags); // (4)
  }
}

I've annotated each important part with a comment and a number, let's look at each of these one by one:

  1. Annotating a class with @PaperParcel will automatically generate the CREATOR and writeToParcel(...) implementations for that class at compile time. These implementations are generated into a java class (in the same package as your model) called PaperParcel$CLASS_NAME$
  2. This is the first usage of the generated code — the generated CREATOR instance.
  3. These are the fields that will be processed by PaperParcel.
  4. This is the second usage of the generated code — the generated writeToParcel(...) implementation.

The fields in this example are all package private, but that is absolutely not a necessity. See Model Conventions for detailed information on how you can structure your model classes.

Manually entering the CREATOR and filling in the Parcelable interface methods is fairly repetitive, so I like to use Intellij Live Templates to generate this code automatically.

Even Easier; Use The AutoValue Extension

If you are already using AutoValue, all you need to do is simply add implements Parcelable to your AutoValue object and that's it:

@AutoValue
public abstract class User implements Parcelable {
  public abstract long id();
  public abstract String firstName();
  public abstract String lastName();

  public static User create(long id, String firstName, String lastName) {
    return new AutoValue_User(id, firstName, lastName);
  }
}

Kotlin

Usage is exactly the same as java:

@PaperParcel
data class User(
    val id: Long,
    val firstName: String,
    val lastName: String
) : Parcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelUser.CREATOR
  }

  override fun describeContents() = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    PaperParcelUser.writeToParcel(this, dest, flags)
  }
}

Optional: If you don't mind a minor amount of reflection, the paperparcel-kotlin module provides PaperParcelable. PaperParcelable is an interface with default implementations written for describeContents and writeToParcel(...) so you don't have to write them yourself, e.g.:

@PaperParcel
data class User(
    val id: Long,
    val firstName: String,
    val lastName: String
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelUser.CREATOR
  }
}

As mentioned in the Java Usage section, Intellij Live Templates can be a really useful tool when writing PaperParcel models. A quick tutorial for setting up Live Templates for PaperParcel in kotlin can be found here.

Migrating from PaperParcel 1.x

If you are a PaperParcel 1.x user and would like to move to 2.x, see Migrating from PaperParcel 1.x.

Type Adapters

Out of the box PaperParcel supports all of the types that are supported by the Parcel class, including a few minor additions (e.g. java.util.Set). Support for any other type can be added using a custom TypeAdapter.

TypeAdapter allows you to define custom parcelling logic for a type. An example for when you might want this is with java.util.Date objects, which by default are parcelled using the built-in SerializationAdapter. A much more performant adapter could be defined as follows:

public final class DateAdapter implements TypeAdapter<Date> {
  // Optional singleton instance. If an adapter has a public static field named
  // INSTANCE, then the singleton instance will be used in the generated code.
  // Doing this can save on unnecessary allocations. For Kotlin users: this is
  // equivalent to defining your adapter as an object instead of a class. 
  public static final DateAdapter INSTANCE = new DateAdapter(); 

  @NonNull @Override public Date readFromParcel(@NonNull Parcel source) {
    return new Date(source.readLong());
  }

  @Override public void writeToParcel(@NonNull Date value, @NonNull Parcel dest, int flags) {
    dest.writeLong(value.getTime());
  }
}

After creating a TypeAdapter, you need to register it with PaperParcel. To learn how, see the Processor Configuration section.

Many similar projects also use some variant of TypeAdapter, however the PaperParcel implementation is slightly more flexible. PaperParcel allows TypeAdapters to be composable and generic. To see why this is useful, let's look at how PaperParcel's built-in SparseArrayAdapter is defined:

public final class SparseArrayAdapter<T> implements TypeAdapter<SparseArray<T>> {
  private final TypeAdapter<T> itemAdapter;

  public SparseArrayAdapter(TypeAdapter<T> itemAdapter) {
    this.itemAdapter = itemAdapter;
  }
  ...
}

As you can see, SparseArrayAdapter has a dependency on another TypeAdapter to handle the parcelling of its items, but the item type is not hard-coded (it is generic). This means we don't need to define a new adapter class each time we use a SparseArray with a different item type, instead this single adapter will handle all item types.

A TypeAdapter can list any number of TypeAdapter or Class dependencies as constructor parameters and PaperParcel will resolve them at compile time. You can take advantage of this power to easily add support for container types that don't come out of the box, e.g. RealmList for Realm, various non-java Collection and Tuple types in Kotlin, or even ImmutableMap for Guava.

Processor Configuration

Configuration of the annotation processor is done though the @ProcessorConfig API. The available customizations are as follows:

@ProcessorConfig can be applied to any annotation, interface, class, or package element in your source code. While this annotation can be applied almost anywhere, the convention is to put it in one of the following places:

E.g.:

@ProcessorConfig(...)
public class MyApp extends Application {
  ...
}

Note that this configuration will only apply to the module that it is defined in. If you have a multi-module project where multiple modules are using PaperParcel, you'll need to define a ProcessorConfig for each of those modules.

Only one ProcessorConfig can be applied per module.

Excluding Fields

By default, PaperParcel will exclude any transient or static field in your models. More advanced exclude APIs are available via PaperParcel.Options.

Model Conventions

Note: this section is only relevant if you are using PaperParcel without the assitance of AutoValue or Kotlin's data classes.

By default, PaperParcel uses no reflection to access fields. Because of that, all of the fields that PaperParcel is going to process need to follow a few loose conventions in order for PaperParcel to know how to read your fields and re-instantiate your models. Any failure to follow these conventions will result in a compile time error with a clear message informing you of what is wrong.

Reading Fields

The easiest way for PaperParcel to read a field is for it to be non-private. Because the generated code lies in the same package as the model itself, default, protected, or public fields can be read directly.

However private fields are common practice and need to be supported. Therefore, if a field is private, PaperParcel will look for an accessor method for that field. PaperParcel relies on the following conventions to find accessor methods:

Writing Fields

The easiest way for PaperParcel to write a field is for it to be non-private and non-final. Because the generated code lies in the same package as the model itself, default, protected, or public fields can be written directly.

As already mentioned, private fields to be supported. Therefore, if a field is private, PaperParcel will look for either a corresponding constructor arugment for the field, or a setter method for the field.

Constructor arugments are simple: they must have the same name as the field that it is assigning. In addition, the argument type must be assignable to the field type.

Setter methods are discovered using similar conventions to the aforementioned accessor method conventions:

Download

Java:

dependencies {
  compile 'nz.bradcampbell:paperparcel:2.0.8'
  annotationProcessor 'nz.bradcampbell:paperparcel-compiler:2.0.8'
}

Kotlin:

apply plugin: 'kotlin-kapt'

dependencies {
  compile 'nz.bradcampbell:paperparcel:2.0.8'
  compile 'nz.bradcampbell:paperparcel-kotlin:2.0.8' // Optional
  kapt 'nz.bradcampbell:paperparcel-compiler:2.0.8'
}

Development snapshots are available on JFrog OSS Artifactory.

Contributing

If you would like to contribute code you can do so by forking the repository and sending a pull request.

When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. Please also make sure your code compiles by running gradlew clean build.

License

Copyright 2016 Bradley Campbell.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.