Generador de implementaciones Parcelable

El complemento kotlin-parcelize proporciona un generador de implementaciones Parcelable.

Para incluir compatibilidad con Parcelable, agrega el complemento de Gradle al archivo build.gradle de tu app:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Cuando anotas una clase con @Parcelize, se genera automáticamente una implementación Parcelable, como se muestra en el siguiente ejemplo:

import kotlinx.parcelize.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable

@Parcelize requiere que todas las propiedades serializadas se declaren en el constructor principal. El complemento emite una advertencia en cada propiedad con un campo de respaldo declarado en el cuerpo de la clase. Además, no puedes aplicar @Parcelize si algunos de los parámetros del constructor principal no son propiedades.

Si tu clase requiere una lógica de serialización más avanzada, escríbela dentro de una clase complementaria:

@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            // Custom write implementation
        }

        override fun create(parcel: Parcel): User {
            // Custom read implementation
        }
    }
}

Tipos admitidos

@Parcelize admite una amplia variedad de tipos:

  • Tipos primitivos (y sus versiones encuadrados)
  • Objetos y enumeraciones
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Todas las implementaciones de Serializable (incluidas Date) y Parcelable
  • Colecciones de todos los tipos compatibles: List (mapeado a ArrayList), Set (mapeado a LinkedHashSet), Map (mapeado a LinkedHashMap)
    • También hay una cantidad de implementaciones concretas: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet y SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Arreglos de todos los tipos compatibles
  • Versiones anulables de todos los tipos compatibles

Parceler personalizados

Si tu tipo no se admite directamente, puedes escribir un objeto de mapeo Parceler.

class ExternalClass(val value: Int)

object ExternalClassParceler : Parceler<ExternalClass> {
    override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())

    override fun ExternalClass.write(parcel: Parcel, flags: Int) {
        parcel.writeInt(value)
    }
}

Puedes aplicar paquetes externos mediante las anotaciones @TypeParceler o @WriteWith:

// Class-local parceler
@Parcelize
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass) : Parcelable

// Property-local parceler
@Parcelize
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass) : Parcelable

// Type-local parceler
@Parcelize
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass) : Parcelable

Crea datos a partir de Parcel

En el código Java, puedes acceder directamente al campo CREATOR.

class UserCreator {
    static User fromParcel(Parcel parcel) {
        return User.CREATOR.createFromParcel(parcel);
    }
}

En Kotlin, no puedes usar el campo CREATOR directamente. En su lugar, usa kotlinx.parcelize.parcelableCreator.

import kotlinx.parcelize.parcelableCreator

fun userFromParcel(parcel: Parcel): User {
    return parcelableCreator<User>().createFromParcel(parcel)
}

Omitir propiedades de la serialización

Si deseas omitir que alguna propiedad se parcele, usa la anotación @IgnoredOnParcel. También se puede usar en propiedades dentro del cuerpo de una clase para silenciar las advertencias que indican que la propiedad no se serializa. Las propiedades del constructor que están marcadas con @IgnoredOnParcel deben tener un valor predeterminado.

@Parcelize
class MyClass(
    val include: String,
    // Don't serialize this property
    @IgnoredOnParcel val ignore: String = "default"
): Parcelable {
    // Silence a warning
    @IgnoredOnParcel
    val computed: String = include + ignore
}

Usa android.os.Parcel.writeValue para serializar una propiedad

Puedes anotar un tipo con @RawValue para que Parcelize use Parcel.writeValue para esa propiedad.

@Parcelize
class MyClass(val external: @RawValue ExternalClass): Parcelable

Esto podría fallar en el tiempo de ejecución si el valor de la propiedad no es compatible de forma nativa con Android.

Es posible que Parcelize también requiera que uses esta anotación cuando no haya otra manera de serializar la propiedad.

Cómo particionar con clases y interfaces selladas

Parcelize requiere que una clase no sea abstracta. Esta limitación no se aplica a las clases selladas. Cuando se usa la anotación @Parcelize en una clase sellada, no es necesario repetirla para las clases derivadas.

@Parcelize
sealed class SealedClass: Parcelable {
    class A(val a: String): SealedClass()
    class B(val b: Int): SealedClass()
}

@Parcelize
class MyClass(val a: SealedClass.A, val b: SealedClass.B, val c: SealedClass): Parcelable

Cómo configurar Parcelize para Kotlin multiplataforma

Antes de Kotlin 2.0, podías usar Parcelize. Para ello, asignaba un alias a las anotaciones de Parcelize con expect y actual:

// Common code
package example

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
expect annotation class MyParcelize()

expect interface MyParcelable

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()

@MyParcelize
class MyClass(
    val x: String,
    @MyIgnoredOnParcel val y: String = ""
): MyParcelable

// Platform code
package example

actual typealias MyParcelize = kotlinx.parcelize.Parcelize
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel

En Kotlin 2.0 y versiones posteriores, no se admiten las anotaciones de alias que activan complementos. Para evitar esto, proporciona una nueva anotación Parcelize como el parámetro additionalAnnotation al complemento.

// Gradle build configuration
kotlin {
    androidTarget {
        compilerOptions {
            // ...
            freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:additionalAnnotation=example.MyParcelize")
        }
    }
}
// Common code
package example

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
// No `expect` keyword here
annotation class MyParcelize()

expect interface MyParcelable

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()

@MyParcelize
class MyClass(
    val x: String,
    @MyIgnoredOnParcel val y: String = ""
): MyParcelable

// Platform code
package example

// No typealias for MyParcelize here
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel

Debido a que la interfaz Parcel solo está disponible en Android, Parcelize no generará ningún código en otras plataformas, por lo que cualquier implementación de actual puede estar vacía. Tampoco es posible usar ninguna anotación que requiera hacer referencia a la clase Parcel, por ejemplo, @WriteWith, en el código común.

Funciones experimentales

Serializador de clases de datos

Disponible a partir de Kotlin 2.1.0.

La anotación DataClass permite serializar clases de datos como si estuvieran anotadas con Parcelize. Esta anotación requiere la habilitación de kotlinx.parcelize.Experimental.

@file:OptIn(kotlinx.parcelize.Experimental::class)

data class C(val a: Int, val b: String)

@Parcelize
class P(val c: @DataClass C) : Parcelable

Se debe poder acceder al constructor principal y a todas sus propiedades desde la clase Parcelable. Además, Parcelize debe admitir todas las propiedades del constructor principal de la clase de datos. Custom Parcelers, si se eligen, deben especificarse en la clase Parcelable, no en la clase de datos. Si la clase de datos implementa Serializable al mismo tiempo, la anotación @DataClass tiene prioridad: no se usará android.os.Parcel.writeSerializable.

Un caso de uso práctico para esto es la serialización de kotlin.Pair. Otro ejemplo útil es simplificar el código multiplataforma: el código común podría declarar la capa de datos como clases de datos, que el código de Android podría aumentar con la lógica de serialización, lo que elimina la necesidad de anotaciones específicas de Android y alias de tipo en el código común.

// Common code:
data class MyData(val x: String, val y: MoreData)
data class MoreData(val a: String, val b: Int)

// Platform code:
@OptIn(kotlinx.parcelize.Experimental::class)
@Parcelize
class DataWrapper(val wrapped: @DataClass MyData): Parcelable

Parámetros non val o var en el constructor principal

Disponible desde Kotlin 2.1.0.

Para habilitar esta función, agrega experimentalCodeGeneration=true a los argumentos del plugin de parcelización.

kotlin {
    compilerOptions {
        // ...
        freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:experimentalCodeGeneration=true")
    }
}

Esta función quita la restricción de que los argumentos del constructor principal deban ser val o var. Esto resuelve un problema de usar parcelize con herencia, que antes requería el uso de propiedades open.

// base parcelize
@Parcelize
open class Base(open val s: String): Parcelable

@Parcelize
class Derived(
    val x: Int,
    // all arguments have to be `val` or `var` so we need to override
    // to not introduce new property name
    override val s: String
): Base(s)

// experimental code generation enabled
@Parcelize
open class Base(val s: String): Parcelable

@Parcelize
class Derived(val x: Int, s: String): Base(s)

Estos parámetros solo se pueden usar en argumentos del constructor de la clase base. No se permite hacer referencia a ellos en el cuerpo de la clase.

@Parcelize
class Derived(s: String): Base(s) { // allowed
    @IgnoredOnParcel
    val x: String = s // ERROR: not allowed.
    init {
        println(s) // ERROR: not allowed
    }
}

Comentarios

Si tienes algún problema con el complemento kotlin-parcelize de Gradle, informa un error.