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:
- Annotating a class with
@PaperParcel
will automatically generate theCREATOR
andwriteToParcel(...)
implementations for that class at compile time. These implementations are generated into a java class (in the same package as your model) calledPaperParcel$CLASS_NAME$
- This is the first usage of the generated code — the generated
CREATOR
instance. - These are the fields that will be processed by PaperParcel.
- 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 TypeAdapter
s 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:
- Extend the type system by adding custom
TypeAdapter
s. - Modify the field exclusion rules.
- Allow the use of reflection to access specified private constructors or fields.
- Disable
Serializable
support. - Enable Lombok support.
@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:
- A custom
android.app.Application
class. - An empty interface named
PaperParcelConfig
. - A
package-info.java
file.
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:
- The method needs to return an assignable type to the field's type.
- The method needs to have no arguments.
- The method needs to have one of the following names:
$FIELD_NAME$
,get$FIELD_NAME$
, oris$FIELD_NAME$
. For example, if the field is namedfirstName
, then the set of valid accessor method names would containfirstName
,getFirstName
, andisFirstName
.
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:
- The method needs to have one argument.
- The argument type needs to be assignable to the field's type.
- The method needs to have one of the following names:
$FIELD_NAME$
, orset$FIELD_NAME$
. For example, if the field is namedfirstName
, then the set of valid setter method names would containfirstName
andsetFirstName
.
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.