Skip to content

Kotlin Usage

Bradley Campbell edited this page Nov 27, 2016 · 23 revisions

Important: all information in this on this page is only relevant to PaperParcel 1.x. For information on the latest version of PaperParcel, visit http://grandstaish.github.io/paperparcel/

Basic usage:

PaperParcel works by building a Parcelable wrapper for each class annotated with @PaperParcel, e.g.

@PaperParcel
data class Example(
  val test: Int
)

This example generates a class named ExampleParcel (the name is always in the format of {ClassName}Parcel) which is Parcelable and has the code to read and write an Example object to and from a Parcel. ExampleParcel has a public constructor that takes in an Example instance and has a public member variable named data in which you can retrieve your data class from later. The usage for this is simple, just "wrap" your instance in the generated wrapper class and pass it through an Intent or Bundle like so:

val example = Example(42)
savedInstanceState.putParcelable("example", ExampleParcel(example))

Then extract it later:

var example: Example? = null
savedInstanceState?.let {
  example = it.getParcelable<ExampleParcel>("example").data
}

Using PaperParcels to wrap/unwrap your data classes

Rather than needing to use the generated wrappers directly, it may be preferable to use the convenience class PaperParcels to wrap/unwrap your objects instead.

Simply wrap like so:

val example = Example(42)
savedInstanceState.putParcelable("example", PaperParcels.wrap(example))

And extract using:

var example: Example? = null
savedInstanceState?.let {
  example = PaperParcels.unwrap(it.getParcelable<TypedParcelable<Example>>("example"))
}

Making data classes Parcelable

When working with wrapper APIs, like described above, it can make it tricky to work with code that you don't control because the receiving code won't know how to unwrap it. Hence making your data classes implement Parcelable themselves is the preferred technique. Luckily, PaperParcel makes this easy using the PaperParcelable interface found in the paperparcel-kotlin module.

PaperParcelable can be used to make your data classes Parcelable like so:

@PaperParcel 
data class Example(
  val test: Int,
  ...
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelable.Creator(Example::class.java)
  }
}

Note that the CREATOR field is boilerplate code and is a great candidate for a Live Template. I wrote a quick tutorial in the wiki to help set it up.

PaperParcelable uses the aforementioned PaperParcels class to do the wrapping and unwrapping internally, so make sure you don't forget the @PaperParcel annotation on your data class because it'll require that there is a generated wrapper at runtime.

Generating a wrapper for a type makes it known to PaperParcel

PaperParcel allows you to use other @PaperParcel classes as properties without them being Parcelable themselves, e.g.:

// This class is Parcelable as it is going to be passed directly to 
// an Intent or Bundle without using the wrapping APIs in the calling 
// and receiving code.
@PaperParcel
data class Person(
  val id: Long,
  val name: String,
  val contactInfo: ContactInfo
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelable.Creator(Person::class.java)
  }
}

// This class isn't Parcelable, but PaperParcel will use the generated 
// wrapper for this class when parcelling Person
@PaperParcel
data class ContactInfo(
  val phoneNumber: String,
  val address: String
)

This is encouraged to help reduce boilerplate code.

Kotlin object declarations

Kotlin objects are supported, as well as data classes. The API is no different, e.g.:

To generate a wrapper:

@PaperParcel
object Example

Or to make it Parcelable itself:

@PaperParcel
object Example : PaperParcelable {
  @JvmField val CREATOR = PaperParcelable.Creator(Example::class.java)
}

Additionally, TypeAdapters can be objects too:

@DefaultAdapter
object DateTypeAdapter : TypeAdapter<Date> {
  override fun writeToParcel(value: Date, outParcel: Parcel, flags: Int) {
    outParcel.writeLong(value.time)
  }

  override fun readFromParcel(inParcel: Parcel): Date {
    return Date(inParcel.readLong())
  }
}

Real world example

The example apps are currently quite simplistic. For a more real-world example, see here

Caveats

If you add additional (non-constructor) properties to your data class, ensure to make them transient (via @Transient). This is to work around a bug in kapt and won't be required once the bug has been fixed. E.g.:

@PaperParcel
data class Example(
  val test: Int
) : PaperParcelable {
  ...
  @delegate:Transient val somethingElse by lazy { ... }
}