Question

Make Kotlin Data Classes Parcelable in Android with @Parcelize

kotlinandroidparcelable

Question

In an Android project using Kotlin, is there a convenient way to make a data class implement Parcelable without manually writing writeToParcel, describeContents, and CREATOR code?

For example, how can this class be made parcelable with minimal boilerplate?

data class User(
    val id: Long,
    val name: String
)

Short Answer

You will learn how Android parceling works, why Kotlin data classes do not become parcelable automatically, and how the @Parcelize compiler plugin generates the required Parcelable implementation for you.

Concept

Parcelable is an Android interface used to serialize an object into a Parcel: a compact container Android uses to transfer data between components such as activities, fragments, services, and saved state.

A Kotlin data class automatically generates useful methods such as equals(), hashCode(), toString(), copy(), and componentN(). However, those generated methods are unrelated to Android's Parcelable interface. A data class is not parcelable unless you explicitly make it so.

The modern Kotlin solution is @Parcelize. It is a Kotlin compiler plugin that generates the parceling code at compile time. You write the class properties and annotate the class; Kotlin generates the otherwise repetitive Parcelable implementation.

This matters because manual Parcelable code is easy to get wrong, especially when a class changes over time. With @Parcelize, the generated implementation stays aligned with the properties declared in the class.

Mental Model

Think of a data class as a neatly labeled form containing information, such as a user's ID and name.

A Parcel is like a small sealed delivery box that Android can carry from one screen to another. A normal data class tells Kotlin how to compare and print the form, but it does not tell Android how to pack the form into the box.

@Parcelize is an automatic packing machine. You describe the fields once, and the compiler creates the packing and unpacking instructions for Android.

Take Quiz

Syntax and Examples

First, enable the Parcelize plugin in your Android app module's Gradle configuration.

plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("plugin.parcelize")
}

Then import Parcelize, implement Parcelable, and add the annotation:

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class User(
    val id: Long,
    val name: String
) : Parcelable

@Parcelize instructs the Kotlin compiler to generate the required parceling code. The : Parcelable part is still important: it declares that callers may use a User wherever Android expects a parcelable value.

You can use the object in an Intent:

val user = User(id = 42L, name = "Mina")

val intent = Intent(this, ProfileActivity::class.java)
intent.putExtra("user", user)
startActivity(intent)

Retrieve it on a recent Android API level with the type-safe overload:

val user = intent.getParcelableExtra("user", User::class.java)

For compatibility code that supports Android versions before API 33, use AndroidX IntentCompat:

import androidx.core.content.IntentCompat

val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)

Step by Step Execution

Consider this class:

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class Product(
    val sku: String,
    val quantity: Int,
    val inStock: Boolean
) : Parcelable

When the app is compiled:

  1. data class causes Kotlin to generate equals, hashCode, toString, copy, and destructuring functions.
  2. : Parcelable declares that Product must satisfy Android's parcelable contract.
  3. @Parcelize activates generated parceling support for this class.
  4. The compiler generates code that writes sku, quantity, and inStock into a Parcel in a consistent order.
  5. It also generates code that reads those values back in the same order and constructs a new Product.

At runtime:

val original = Product("keyboard-01", 2, true)
intent.putExtra("product", original)

Android parcels the object when it needs to pass it across the component boundary. In the destination:

val received = IntentCompat.getParcelableExtra(
    intent,
    "product",
    Product::class.java
)

received is a new Product instance containing the same property values as original.

Real World Use Cases

Common uses for parcelable data classes include:

  • Navigation arguments: Pass a small selected item, such as a Product or User, to a detail screen.
  • Activity and fragment arguments: Provide screen configuration or an initial selection.
  • Service communication: Send a small request or result object through Android component APIs that require Parcelable.
  • Saved UI state: Store lightweight screen state in a Bundle so it can be restored after recreation.
  • Custom views: Save and restore a view's state when Android recreates the UI.

For example, a checkout screen might receive a parcelable order draft:

@Parcelize
data class OrderDraft(
    val customerId: String,
    val itemIds: List<String>,
    val couponCode: String?
) : Parcelable
Take Quiz

Real Codebase Usage

In production code, @Parcelize is typically used for small objects that cross an Android framework boundary. Keep those objects focused on the values the receiving component actually needs.

A common navigation pattern is to pass an identifier rather than a complete database model:

@Parcelize
data class ProductArgs(
    val productId: Long
) : Parcelable

The destination reads productId and loads current data from a repository. This avoids several problems:

  • Large parcelables are slower to copy.
  • The passed object may be stale when the user reaches the next screen.
  • UI navigation becomes less tightly coupled to a database or network model.

Use validation and guard clauses after receiving an argument when appropriate:

val args = IntentCompat.getParcelableExtra(intent, "product_args", ProductArgs::class.java)
    ?: return finish()

viewModel.loadProduct(args.productId)

For state that must survive process death, make sure every value stored in a Bundle is supported by Android's state-saving system. Parcelable can help, but it is still best to save only the minimum state needed to restore the screen.

Common Mistakes

Forgetting : Parcelable

The annotation does not replace the interface declaration.

// Incorrect
@Parcelize
data class User(val id: Long)
// Correct
@Parcelize
data class User(val id: Long) : Parcelable

Forgetting the Gradle plugin

If @Parcelize is unresolved or the compiler does not generate the implementation, add the Parcelize plugin to the module that contains the class:

plugins {
    kotlin("plugin.parcelize")
}

Using the old kotlin-android-extensions approach

Older tutorials may use kotlin-android-extensions or an experimental parcelize package. Do not add that deprecated plugin for new projects. Use kotlin("plugin.parcelize") and kotlinx.parcelize.Parcelize.

Passing large object graphs

Avoid putting large lists, images, database entities with many relationships, or large JSON-like payloads into an Intent or Bundle. Android transactions have size limits, and oversized data can cause runtime failures. Pass an ID, URI, or small set of arguments instead.

Assuming every custom type works automatically

Primitive values, strings, many collections, and parcelable nested objects are commonly supported. A custom property type may need to implement Parcelable, be serializable through another supported mechanism, or use a custom parceler.

@Parcelize
data class ScreenArgs(
    val user: User
) : Parcelable

@Parcelize
data class User(
    val id: Long,
    val name: String
) : Parcelable

Reading an extra without checking for null

An extra can be absent, malformed, or unavailable. Treat it as nullable unless your navigation layer guarantees it.

val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)
    ?: return finish()

Comparisons

ApproachBest forAdvantagesDrawbacks
@ParcelizeAndroid component arguments and small saved stateMinimal boilerplate; Android-nativeAndroid-specific; should not carry large data
Manual ParcelableRare special serialization requirementsFull control over how values are writtenVerbose and error-prone
SerializableUsually legacy Java code onlySimple interface declarationGenerally less efficient for Android IPC-style transfers
Passing an IDMost navigation and detail screensSmall, current data can be loaded at destinationRequires a repository or data source lookup
Bundle primitivesA few simple valuesNo custom class requiredBecomes difficult to maintain as arguments grow

@Parcelize and data class solve different jobs. A data class reduces value-object boilerplate; @Parcelize generates Android parceling boilerplate. They work well together but neither replaces the other.

Take Quiz

Cheat Sheet

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class User(
    val id: Long,
    val name: String,
    val isAdmin: Boolean = false
) : Parcelable

Module plugin:

plugins {
    kotlin("plugin.parcelize")
}

Pass through an intent:

intent.putExtra("user", user)

Read compatibly with AndroidX:

val user = IntentCompat.getParcelableExtra(intent, "user", User::class.java)

Rules:

  • Add both @Parcelize and : Parcelable.
  • Import kotlinx.parcelize.Parcelize.
  • Keep parcelables small.
  • Prefer IDs for large, mutable, or repository-backed data.
  • Nested custom objects should also be parcelable or otherwise supported.
  • Treat received extras as nullable unless guaranteed by your app's contract.

FAQ

Does Kotlin make every data class Parcelable automatically?

No. Data classes generate value-related functions, not Android parceling code. Add @Parcelize and : Parcelable.

What dependency is needed for @Parcelize?

In a standard Android Kotlin module, enable the Kotlin Parcelize Gradle plugin with kotlin("plugin.parcelize"). Then import kotlinx.parcelize.Parcelize.

Is @Parcelize better than writing Parcelable manually?

For ordinary value classes, yes. It removes repetitive code and reduces the chance that writing and reading order get out of sync. Write manual parceling only when you need special control.

Can a parcelable data class contain another data class?

Yes, if the nested custom type is also parcelable or is otherwise supported by the generated parceling mechanism.

Should I pass a complete API or database model between activities?

Usually no. Prefer a small argument object or an ID, then load the current data in the destination screen. This keeps parcels small and reduces stale-data issues.

Is Serializable a replacement for Parcelable on Android?

It can work in some cases, but Parcelable is the Android-focused mechanism for this purpose. For Kotlin Android code, @Parcelize is usually the convenient choice.

Why is my parcelable extra null?

The sender may not have added the extra, the key may differ, or the object may not match the expected type. Use one shared constant or a typed navigation mechanism and handle missing values safely.

Take Quiz

Mini Project

Description

Build a small profile-launch flow. The first activity creates a ProfileArgs data class and sends it to a profile activity. The profile activity safely reads the parcelable argument and displays its values. This demonstrates a realistic use of @Parcelize for small navigation arguments.

Goal

Pass a typed profile argument between two Android activities without writing manual Parcelable methods.

Requirements

Create a ProfileArgs data class with an ID, display name, and premium-status flag. Enable the Parcelize plugin and make ProfileArgs implement Parcelable using @Parcelize. Send a ProfileArgs instance from MainActivity to ProfileActivity in an intent extra. Read the extra safely in ProfileActivity using IntentCompat. Close ProfileActivity if the required argument is missing.

Take Quiz

Keep learning

Related questions

View more