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
(incluidasDate
) yParcelable
- Colecciones de todos los tipos compatibles:
List
(mapeado aArrayList
),Set
(mapeado aLinkedHashSet
),Map
(mapeado aLinkedHashMap
)- También hay una cantidad de implementaciones concretas:
ArrayList
,LinkedList
,SortedSet
,NavigableSet
,HashSet
,LinkedHashSet
,TreeSet
ySortedMap
,NavigableMap
,HashMap
,LinkedHashMap
,TreeMap
,ConcurrentHashMap
- También hay una cantidad de implementaciones concretas:
- 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.