A study of the Parcelize feature from Kotlin Android Extensions

Life is too short to waste time on writing Parcelable code

Christophe Beyls
18 min readNov 19, 2019

Two years ago, I wrote about how you can leverage features of the Kotlin programming language to manually write your Android Parcelable implementations in the most concise and readable way. Does it mean that I always prefer writing this code manually rather than letting a library or tool generate it for me? Of course not: like most developers, I believe that the best code is the code you don’t have to write. But I expect the tools I use to meet my quality standards.

That’s why I tend to be conservative about the dependencies I add to my projects and will always favor official libraries from Jetbrains or Google over third-party solutions. Things have changed for the better since I wrote the previous article: with the release of Kotlin 1.3.40, @Parcelize is now a stable feature provided by the Parcelize Gradle plugin (formerly known as Kotlin Android Extensions). And since version 1.3.60, the Android Studio plugin also properly recognizes the feature as non-experimental so it can finally be considered as production-ready.

I previously enumerated a list of some of the negative aspects of third-party Parcelable code generation libraries compared to manual implementation. Here’s why I believe @Parcelize stands out from the rest in regard to that list:

  • It’s an official plugin made by JetBrains with the collaboration of Google and is guaranteed to be well supported in the future
  • The generated code of @Parcelize is very efficient (as we’ll discover further in this article)
  • The CREATOR field doesn’t have to be declared at all, along with that easy-to-forget @JvmField annotation
  • Thanks to the Parceler interface, it is possible to write simple plugins to handle unsupported types or override the default implementation
  • No extra classes are created by the plugin. All the generated code is embedded in the annotated class so the app will behave exactly as if you wrote the code yourself
  • There is no runtime library overhead and zero extra methods are added to your app.

Project setup

First, you should upgrade your Kotlin Gradle plugins and Android Studio plugin to version 1.3.60 or more recent.

For Kotlin 1.3.60 to 1.4.10

To enable the “Parcelable implementation generator” feature, you have to enable the Kotlin Android Extensions Gradle plugin in your project by simply declaring it at the top of your module’s build.gradle file, just after the kotlin-android plugin:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

By using the newer version of the plugin, it’s not necessary anymore to turn on experimental mode to be able to use the @Parcelize feature or for the IDE to detect it as available.

However, I’m not a big fan of the other features provided by this plugin like the View binding and caching. By default, this will scan your layouts and generate static properties for every View with an id, as well as adding a hidden View cache in your Activities and Fragments. I don’t want it. Fortunately, there is a way to enable Parcelize while disabling other features of the plugin. Add this extra section to the same build.gradle file, below the android section:

androidExtensions {
features = ["parcelize"]
}

Since Kotlin 1.4.20

All other features of the Kotlin Android Extensions Gradle plugin have been deprecated since Kotlin 1.4.20 and Parcelize has moved to its own Gradle plugin. Enable it by declaring it at the top of your module’s build.gradle file, just after the kotlin-android plugin:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'

We’re all set to use the @Parcelize annotation.

Usage

Just add the @Parcelize annotation to a class implementing the Parcelable interface and the Parcelable implementation will be generated automatically.

@Parcelize
class Person(val name: String, val age: Int) : Parcelable

This is the same example as in the previous article, in just 2 lines of code.

The class can be a data class but it’s optional. All properties to serialize have to be declared in the primary constructor, and like a data class all parameters of the primary constructor need to be properties. They can be val or var, private or public.

The main limitation of the feature is that it is not allowed to annotate abstract or sealed classes. To make them implement the Parcelable interface you can annotate each of their child classes instead. We’ll go further into details with concrete examples in a following chapter.

Analyzing the generated code

This looks fine so far, but what about the quality of the generated code? Is the implementation as efficient as it could be? Let’s find out.

We’re going to use the Kotlin Bytecode inspector on the annotated class and decompile it back to Java to reveal the code under the hood.

Basic types

@Parcelize
class Basic(val aByte: Byte, val aShort: Short,
val anInt: Int, val aLong: Long,
val aFloat: Float, val aDouble: Double,
val aBoolean: Boolean, val aString: String) : Parcelable

The following code will be generated:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeByte(this.aByte);
parcel.writeInt(this.aShort);
parcel.writeInt(this.anInt);
parcel.writeLong(this.aLong);
parcel.writeFloat(this.aFloat);
parcel.writeDouble(this.aDouble);
parcel.writeInt(this.aBoolean);
parcel.writeString(this.aString);
}

For basic types like primitives and String, we see that the proper dedicated methods from the Parcel class are used as expected. Notice that the Boolean value is written as an Int. This is because at the Java bytecode level, a boolean is represented as a primitive integer whose value is either 0 or 1. The programming language hides this abstraction. Also, the Short value is written as an Int as well, because the Parcel API doesn’t provide a way to serialize Short values so the next bigger integer type is used instead.

The code which reads back the values looks like this:

public static class Creator implements android.os.Parcelable.Creator {
@NotNull
public final Object[] newArray(int size) {
return new Basic[size];
}

@NotNull
public final Object createFromParcel(@NotNull Parcel in) {
return new Basic(in.readByte(), (short)in.readInt(), in.readInt(), in.readLong(), in.readFloat(), in.readDouble(), in.readInt() != 0, in.readString());
}
}

Besides the above code, no additional constructor or method is added to the base class and all of the Parcelable deserialization code fits inside the createFromParcel() method of the Creator class. This guarantees that the number of methods in the application’s APK file will be as low as possible, which is always important.

Nullable types

Here is another Kotlin sample with various types of nullable fields.

@Parcelize
class NullableFields(
val aNullableInt: Int?,
val aNullableFloat: Float?,
val aNullableBoolean: Boolean?,
val aNullableString: String?
) : Parcelable

The generated serialization code decompiles to this:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
Integer var10001 = this.aNullableInt;
if (var10001 != null) {
parcel.writeInt(1);
parcel.writeInt(var10001);
} else {
parcel.writeInt(0);
}

Float var3 = this.aNullableFloat;
if (var3 != null) {
parcel.writeInt(1);
parcel.writeFloat(var3);
} else {
parcel.writeInt(0);
}

Boolean var4 = this.aNullableBoolean;
if (var4 != null) {
parcel.writeInt(1);
parcel.writeInt(var4);
} else {
parcel.writeInt(0);
}

parcel.writeString(this.aNullableString);
}

For nullable counterparts of primitive types, the plugin generates code which writes an extra integer before the value to indicate if it’s null or not, which is just as efficient as a manual implementation. As for the String? value, Parcel.writeString() already properly handles null values so no integer is added to serialize this type.

Nullable fields are handled properly and efficiently for every type.

Nested Parcelables

Let’s declare a Parcelable class embedding a Parcelable field.

@Parcelize
class Book(val title: String, val author: Person) : Parcelable

Person is the class we defined earlier, which is also Parcelable. Here is the representation of the serialization code:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeString(this.title);
this.author.writeToParcel(parcel, 0);
}

Interesting: the plugin generated a direct nested call to writeToParcel(). This is equivalent to calling Parcel.writeTypedObject(). It’s very efficient because it allows skipping writing the class name of the nested type like Parcel.writeParcelable() does. This information is normally used during deserialization in order to perform a small reflection lookup to retrieve the CREATOR field corresponding to the correct subtype.

But in this case the CREATOR field to use is well-known and unique, because the Person class is final. That’s why the deserialization code looks like this:

@NotNull
public final Object createFromParcel(@NotNull Parcel in) {
return new Book(in.readString(), (Person)Person.CREATOR.createFromParcel(in));
}

For non-final classes or interfaces, the Parcelize plugin will generate a call to Parcel.writeParcelable() instead.

Classes are final by default in Kotlin, allowing optimized nested serialization to be used in most cases. If you are including a Java Parcelable class as a field of a Kotlin class annotated with @Parcelize, declare the class as final whenever possible to be able to benefit from the same optimization.

Enum classes

enum classes are supported out-of-the-box by the plugin.

enum class State {
ON, OFF
}

@Parcelize
class PowerSwitch(var state: State) : Parcelable

Serialization code:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeString(this.state.name());
}

The enum value name is written as a String and the deserialization code uses Enum.valueOf(Class enumType, String name) to transform the name back to an enum value. This is definitely more efficient than using writeSerializable() which must be avoided at all costs on Android for performance and efficiency reasons. Internally, the Enum implementation uses a name cache to quickly retrieve an enum value from its name.

Another way of efficiently serializing an enum value would be to store its ordinal() integer, with the downside of having the values array cloned each time EnumType.values() is called during deserialization. In summary, the code generated by the plugin is fine in terms of performance.

Bonus feature: make enum classes implement Parcelable

You can actually annotate an enum class itself if you make it implement Parcelable.

@Parcelize
enum class State : Parcelable {
ON, OFF
}

Unlike regular classes whose properties are serialized, the enum classes have their value name serialized so that the proper value instance is retrieved from memory on deserialization (since enum values are singletons).

This is particularly handy when you want to put an enum value in a Bundle (as an Intent extra or Fragment argument for example), without having to use Bundle.putSerializable() which is slower and less efficient and without having to write your own helper methods for custom serialization. Now you can just write:

val args = Bundle(1).apply {
putParcelable("state", state)
}

Or even simpler, you can use the bundleOf() factory function from the core-ktx library and the argument will be serialized as a Parcelable automatically, because the Parcelable interface has a higher priority than Serializable in the function.

val args = bundleOf("state" to state)

Collections

@Parcelize supports a wide range of collections by default:

  • All array types (except ShortArray)
  • List, Set, Map interfaces (mapped to ArrayList, LinkedHashSet and LinkedHashMap respectively)
  • ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Android-specific framework collections: SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray.

For the following types: ByteArray, IntArray, CharArray, LongArray, FloatArray, DoubleArray, BooleanArray, Array<String>, List<String>, Array<Binder>, the generated code will simply call the dedicated optimized methods from the Parcel API. For other array and collection types, things work differently.

Here is an example of a class including a collection of objects implementing Parcelable.

@Parcelize
class Library(val books: List<Book>) : Parcelable

The bytecode inspector reveals that the generated code doesn’t use Parcel.writeTypedList() from the Parcel API but instead inlines the logic to handle the list directly in the body of the writeToParcel() method:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
List var10002 = this.books;
parcel.writeInt(var10002.size());
Iterator var10000 = var10002.iterator();

while(var10000.hasNext()) {
((Book)var10000.next()).writeToParcel(parcel, 0);
}
}

Same goes for the deserialization code. This way of doing things actually has two benefits:

  • It allows Parcelize to support more collection types than the ones provided by the Parcel collections API, without adding extra methods to the code.
  • Serialization will be more efficient for many types compared to the Parcel collections API implementation because the compiler plugin will always use the best serialization method for each value type in the collection. For example, serializing a SparseArray<Book> (where Book is a final class) using Parcel.writeSparseArray() will result in Parcel.writeValue() to be used to serialize the Book type. This method is less efficient because for each value it will write an extra integer describing the value type (which is unnecessary because all values are of the same type in this case), then use Parcel.writeParcelable() which is also less efficient than a nested call to Book.writeToParcel() as we saw earlier. In comparison, the Parcelize plugin will just generate a call to Book.writeToParcel() for each value.

Note that ArrayMap, ArraySet, LongSparseArray from the Android framework are not supported by default, as well as all the unbundled collection classes provided by the Jetpack libraries: ArrayMap, ArraySet, LongSparseArray, SimpleArrayMap, SparseArrayCompat. Consider using SparseArray instead of SparseArrayCompat, and write your own custom serializer for the other types if you need them.

Finally, ArrayDeque, EnumMap and EnumSet may look like they are supported, but it’s only because they implement the Serializable interface and the generated code will just fall back to using Parcel.writeSerializable() which is slow and inefficient. Therefore it’s highly recommended to either use more generic collection types like Set<Enum> or write your own custom serializer, as we’ll see in the next chapter.

When including collections in your Parcelable objects, make sure their type is properly supported by the plugin or a custom serializer, especially when it implements the Serializable interface.

Custom types serializers

The Parcelize plugin also provides support for the remaining types that are part of the Parcel API: CharSequence, Exception (which only supports a few types of Exceptions), Size, SizeF, Bundle, IBinder, IInterface and FileDescriptor.

For all other types, it will default to using Parcel.writeSerializable() if the type implements Serializable (which — again — is bad for performance and produces a large amount of data, so must be avoided). If it doesn’t implement Serializable, a Lint warning will be shown and Parcel.writeValue() will be used instead, ultimately failing at runtime.

To avoid both of the above cases, you should provide your own custom type serializer implementation in the form of an object implementing the Parceler interface. Here’s a simple example for the Date type:

object DateParceler : Parceler<Date> {

override fun create(parcel: Parcel) = Date(parcel.readLong())

override fun Date.write(parcel: Parcel, flags: Int)
= parcel.writeLong(time)
}

Then, in order to make the plugin use that Parceler implementation to serialize and deserialize properties using the Date type, you need to add an extra annotation to your Parcelable objects. You can either annotate the type of a property with @WriteWith<ParcelerType>, or annotate the property or the whole class with @TypeParceler<PropertyType, ParcelerType>.

  • Annotating the Parcelable class allows to group annotations together and avoid repetition, especially if you have many properties of the same type for which you want a custom serializer.
@Parcelize
@TypeParceler<Date, DateParceler>
class Session(val title: String,
val startTime: Date,
val endTime: Date): Parcelable
  • Annotating the property type avoids any ambiguity regarding which implementation is going to be used to serialize a property. If there is a mismatch between the annotation and the type, a Lint warning will be shown in the IDE (but it won’t prevent compilation).
@Parcelize
class Session(
val title: String,
val startTime: @WriteWith<DateParceler> Date,
val endTime: @WriteWith<DateParceler> Date
) : Parcelable

Let’s have a look at the generated code to check that the custom Parceler implementation is being used in place of writeSerializable() for the dates:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeString(this.title);
DateParceler.INSTANCE.write(this.startTime, parcel, flags);
DateParceler.INSTANCE.write(this.endTime, parcel, flags);
}

Handling nullable custom types

Even though Parcelize provides support for serializing the nullable variant of all built-in types, it doesn’t add automatic support for the nullable variant of a custom type based on its non-null custom type Parceler. This means that if you provide a Parceler implementation for the Date type, it won’t support Date? properties automatically unless you add another annotation for the Date? type as well.

We can verify this by making the endTime property nullable in our class…

@Parcelize
@TypeParceler<Date, DateParceler>
class Session(val title: String,
val startTime: Date,
val endTime: Date?): Parcelable

… and then checking the generated code again:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeString(this.title);
DateParceler.INSTANCE.write(this.startTime, parcel, flags);
parcel.writeSerializable(this.endTime);
}

Indeed, DateParceler is not used for the endTime property. To fix this, another @TypeParceler (or @WriteWith) annotation needs to be added for the nullable Date? type and the Parceler implementation needs to properly handle nullable values as well.

Fortunately, it’s not needed to provide two separate Parceler implementations for nullable and non-null types: you can use a single one designed for nullable types to handle non-null types as well.

Here is a sample collection of Parceler implementations which are able to serialize and deserialize nullable and non-null variants of Date, BigInteger and BigDecimal types.

When creating a custom Parceler implementation, prefer declaring the nullable variant of the type to be able to use it for both nullable and non-null properties.

Going back to our example, we can now use the revised DateParceler implementation to handle nullable and non-null dates by adding annotations for both types:

@Parcelize
@TypeParceler<Date, DateParceler>
@TypeParceler<Date?, DateParceler>
class Session(val title: String,
val startTime: Date,
val endTime: Date?): Parcelable

and we can confirm that the generated code now uses the custom type serializer for both date properties:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
parcel.writeString(this.title);
DateParceler.INSTANCE.write(this.startTime, parcel, flags);
DateParceler.INSTANCE.write(this.endTime, parcel, flags);
}

Add annotations targeting the correct type variant (nullable or non-null) for each property type requiring a custom serializer in the primary constructor of the Parcelable class. An annotation targeting a non-null type will be ignored for a nullable type, and vice-versa.

Handling generic custom types

One limitation of custom Parceler implementations is that they are required to be object singletons. Which means they can’t be passed extra arguments through a constructor: a separate object needs to be declared for each variation of the serialization algorithm.

But you can work around that limitation and avoid duplicating code by creating reusable utility functions and/or parent classes to be used by these object instances.

Let’s illustrate this with an example by creating a Parceler implementation for EnumSet<E>. The concrete Enum Class is actually needed to be able to instantiate an EnumSet and populate its values during deserialization. We would like to be able to pass that Class as argument to the Parceler at deserialization time but we can’t do it directly. Instead, it can be passed as constructor argument of a reusable Parceler implementation:

open class EnumSetParceler<E : Enum<E>>(private val elementType: Class<E>) : Parceler<EnumSet<E>> {

private val values = elementType.enumConstants

override fun create(parcel: Parcel): EnumSet<E> {
val set = EnumSet.noneOf(elementType)
for (i in 0 until parcel.readInt()) {
set.add(values[parcel.readInt()])
}
return set
}

override fun EnumSet<E>.write(parcel: Parcel, flags: Int) {
parcel.writeInt(size)
for (value in this) {
parcel.writeInt(value.ordinal)
}
}
}

That single open class can then be used as parent for multiple object instances, one for each concrete EnumSet<E> type that needs to be serialized using Parcelize:

object StateEnumSetParceler
: EnumSetParceler<State>(State::class.java)
object CategoryEnumSetParceler
: EnumSetParceler<Category>(Category::class.java)

We end up with a collection of objects that can be used to serialize variants of the same type, without the need to duplicate logic.

Accessing generated CREATOR fields from Kotlin code

When writing custom Parcel deserialization code, for example by implementing a Parceler object, sometimes you need to reference the static CREATOR field of Parcelable classes. It’s useful to create an object instance for a well-known type without relying on reflection, and is used among others by Parcel.createTypedArrayList() and Parcel.readTypedObject().

But sadly you’ll notice that the static CREATOR fields generated by Parcelize are not visible from Kotlin code located in the same module. This is because these fields are added to the classes annotated with @Parcelize at a later stage of the Kotlin compilation process, which means they don’t exist yet when the compiler attempts to resolve references amongst Kotlin files at the beginning of compilation. This issue is known since 2017 and no fix seems to be planned so far, which is a bit surprising for a feature that took years to be called “production-ready”. Anyway, there are a few ways to work around it:

  • Manually write the Parcelable implementation for those specific classes for which you need to access the CREATOR field, instead of having them generated automatically.
  • In case of custom collection types, use standard collection types which are supported out-of-the-box instead.
  • Use the less efficient Parcel methods which resolve the CREATOR fields using reflection: Parcel.readArrayList() in place of Parcel.createTypedArrayList() and Parcel.readParcelable() in place of Parcel.readTypedObject(). The serialized data format of these methods is less compact because the class name is written before each entry. The CREATOR fields are cached by the Parcel instance after the first reflective lookup so the performance impact should be minimal.
  • Rely on Java cross-compilation: the generated CREATOR field can actually be retrieved from a Java class, which can in turn expose it back to Kotlin code. Mixing Kotlin and Java in the same module will make the compilation a bit slower compared to a Kotlin-only module and creating a bridge Java class definitely feels like an ugly hack, but this will allow you to write the most efficient custom type serializers while still relying on generated code.
Sokoban puzzle by Borgar Þorsteinsson. Under Creative Commons license

Dealing with class inheritance

You can annotate open classes with @Parcelize. However, it’s not possible to also annotate child classes that inherit from these open classes without duplicating all the serializable properties of their parent class. Let’s look at an example to have a better understanding of the problem:

@Parcelize
open class Book(val title: String, val author: Person) : Parcelable

@Parcelize
class ElectronicBook(private val _title: String,
private val _author: Person,
val downloadSize: Long) : Book(_title, _author)

In order to comply with the Parcelize restriction of having all primary constructor arguments declared as either val or var, we end up redeclaring two unnecessary properties for title and author which are already handled by the parent class. This works but is far from elegant, not to mention duplicating fields in an object is wasting memory.

If you need to create a child class inheriting from an open Parcelable class, you should either write the Parcelable implementation of that child class manually, or refactor the parent class to make it abstract or sealed.

In the case of the above example, the Book class would be turned into an abstract or sealed class (or even an interface) with a new concrete child class PaperBook.

Parcelable abstract and sealed classes with common fields

It’s not allowed to annotate abstract or sealed classes with @Parcelize: all concrete child classes that inherit from them must be annotated instead.

This has a few implications to keep in mind:

  • It’s not possible to reuse serialization code from a parent class in a child class, meaning that the generated code needed to serialize a field declared in the parent class will effectively be repeated in every child class;
  • All properties that need to be serialized in the abstract or sealed class must be declared as abstract in order to avoid duplicating them in the child class.

Let’s look at an example of a Parcelable abstract class.

abstract class Vehicle(val wheels: Int) : Parcelable {
abstract val model: String
abstract var speed: Float
}

@Parcelize
class Car(override val model: String, override var speed: Float)
: Vehicle(4)

@Parcelize
class Bicycle(override val model: String, override var speed: Float)
: Vehicle(2)
  • The common wheels property should not be serialized and its value is initialized directly by the child classes constructor.
  • The common model and speed properties need to be serialized in each instance, so they are declared as abstract in the parent Vehicle class. Then, they are overridden in the primary constructor of all child classes and will be handled by Parcelize.
  • Extra properties to serialize could also be added in the primary constructor of the child classes.

Objects with a parent class

Another interesting feature of Parcelize that has not yet been mentioned is that it supports annotating object. At first it would seem useless to support the serialization of a singleton, but it actually makes sense when the object has a parent class and represents one of the possible values of its parent class.

This feature is particularly useful when it comes to implement a Parcelable sealed class where some of the values are objects. A typical example of such class would look like this:

sealed class Status : Parcelable

@Parcelize
object Success : Status()

@Parcelize
class Error(val message: String) : Status()

We can take one last look at the bytecode inspector in order to confirm that the generated deserializing code for the object is indeed returning the existing singleton instance and not a new instance:

@NotNull
public final Object createFromParcel(@NotNull Parcel in) {
return in.readInt() != 0 ? Success.INSTANCE : null;
}

Excluding properties from serialization

Sometimes properties are used to store transient state that must be excluded from serialization. In a typical Java world, the containing class would implement the Serializable interface and the excluded fields would be annotated with @Transient.

But in our Android world we are using the oreferred Parcelable interface and this annotation is not recognized by Parcelize. Instead, to be exempted from serialization a Kotlin property has to be declared in the body of the class instead of the primary constructor:

@Parcelize
class Book(val title: String, val totalPages: Int) : Parcelable {
@IgnoredOnParcel
var readPages: Int = 0
}

In this example, the readPages property will be skipped. The IDE will highlight it with a warning message by default, until you annotate it with @IgnoredOnParcel to make that behavior more explicit.

Now, is it still possible to pass the initial value of that property through a class constructor like for the other properties? The answer is yes: by using a secondary constructor which delegates to the primary constructor.

@Parcelize
class Book private constructor(val title: String,
val totalPages: Int) : Parcelable {
@IgnoredOnParcel
var readPages: Int = 0

constructor(title: String, totalPages: Int, readPages: Int)
: this(title, totalPages) {
this.readPages = readPages
}
}

In the above example, the primary constructor was made private so that it’s only callable by the secondary constructor and the generated Parcelable CREATOR, and external classes have to use the secondary constructor with 3 arguments to create an instance.

Similarly, it’s possible to use the same technique to achieve the opposite: make private properties serializable without having to pass their initial value through the public class constructor.

@Parcelize
class ClickCounter private constructor(private var count: Int)
: Parcelable {

constructor() : this(0)

fun click() {
count++
}

val currentValue
get
() = count
}

In this final example, count is an internal state variable that will be initialized to 0 when the class public constructor is called, and restored to its current value upon deserialization.

Use a public secondary constructor in combination with a private primary constructor to control which properties you want to be initializable through the public constructor and which properties you want to be serialized.

And that’s a wrap. This covers everything there is to know about the feature so that, except for rare cases, no Android developer should have to write manual Parcelable serialization code ever again. If you are aware of other caveats or tips and tricks regarding Parcelize, feel free to detail them in the comments section.

Thank you for reading this exceptionally long post. If you liked it, please share or subscribe and stay tuned for more in the coming months.

--

--

Christophe Beyls
Christophe Beyls

Written by Christophe Beyls

Android developer from Belgium, blogging about advanced programming topics.