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 al campo CREATOR directamente.

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)
}

Cómo omitir propiedades de la serialización

Si quieres omitir la serialización de alguna propiedad, usa la anotación @IgnoredOnParcel. También se puede usar en propiedades dentro del cuerpo de una clase para silenciar las advertencias sobre la propiedad que no se serializa. Las propiedades del constructor anotadas 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 te exija usar esta anotación cuando no haya otra forma de serializar la propiedad.

Cómo dividir en paquetes con clases y interfaces selladas

Parcelize requiere que la clase que se va a parcelar 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 Multiplatform

Antes de Kotlin 2.0, podías usar Parcelize creando alias de 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 del 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

Dado 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 allí 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 clase de datos

Disponible desde Kotlin 2.1.0.

La anotación DataClass permite serializar clases de datos como si estuvieran anotadas con Parcelize. Esta anotación requiere que se habilite 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. Si se eligen Parceladores personalizados, se deben especificar 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 la simplificación del 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 lógica de serialización, lo que eliminaría 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 que no son 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 complemento parcelize.

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

Esta función levanta la restricción de que los argumentos del constructor principal deban ser val o var. Esto resuelve un punto problemático del uso de 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)

Solo se permite usar estos parámetros en argumentos para el 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.