Generatore di implementazioni parcelle

Il plug-in kotlin-parcelize fornisce un generatore di implementazione Parcelable.

Per includere il supporto di Parcelable, aggiungi il plug-in Gradle al file build.gradle della tua app:

Alla moda

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Quando annoti una classe con @Parcelize, viene generata automaticamente un'implementazione di Parcelable, come mostrato nell'esempio seguente:

// import kotlinx.parcelize.Parcelize

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

@Parcelize richiede che tutte le proprietà serializzate vengano dichiarate nel costruttore principale. Il plug-in genera un avviso per ogni proprietà con un campo di supporto dichiarato nel corpo della classe. Inoltre, non puoi applicare @Parcelize se alcuni parametri del costruttore primario non sono proprietà.

Se la tua classe richiede una logica di serializzazione più avanzata, scrivila all'interno di una classe complementare:

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

Tipi supportati

@Parcelize supporta un'ampia gamma di tipi:

  • Tipi primitivi (e relative versioni boxed)
  • Oggetti ed enumerazioni
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Tutte le implementazioni di Serializable (incluso Date) e Parcelable
  • Raccolte di tutti i tipi supportati: List (mappato a ArrayList), Set (mappato a LinkedHashSet), Map (mappato a LinkedHashMap)
    • Inoltre, sono disponibili diverse implementazioni concrete: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Array di tutti i tipi supportati
  • Versioni Nullable di tutti i tipi supportati

Parceler personalizzati

Se il tuo tipo non è supportato direttamente, puoi scrivere un oggetto di mappatura 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)
    }
}

Puoi applicare i parceler esterni utilizzando le annotazioni @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

Creare dati da Parcel

Nel codice Java, puoi accedere direttamente al campo CREATOR.

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

In Kotlin, non puoi utilizzare direttamente il campo CREATOR. Utilizza invece kotlinx.parcelize.parcelableCreator.

// import kotlinx.parcelize.parcelableCreator

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

Skip properties from serialization (Escludi proprietà dalla serializzazione)

Se vuoi escludere alcune proprietà dalla suddivisione in pacchetti, utilizza l'annotazione @IgnoredOnParcel. Può essere utilizzato anche nelle proprietà all'interno del corpo di una classe per disattivare gli avvisi relativi alla proprietà non serializzata. Le proprietà del costruttore annotate con @IgnoredOnParcel devono avere un valore predefinito.

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

Utilizza android.os.Parcel.writeValue per serializzare una proprietà

Puoi annotare un tipo con @RawValue per fare in modo che Parcelize utilizzi Parcel.writeValue per quella proprietà.

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

L'operazione potrebbe non riuscire in fase di runtime se il valore della proprietà non è supportato in modo nativo da Android.

Parcelize potrebbe anche richiedere l'utilizzo di questa annotazione quando non esiste un altro modo per serializzare la proprietà.

Parcelizzazione con classi sigillate e interfacce sigillate

Parcelize richiede che una classe da serializzare non sia astratta. Questa limitazione non vale per le classi sigillate. Quando l'annotazione @Parcelize viene utilizzata in una classe sealed, non è necessario ripeterla per le classi derivate.

@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

Configurare Parcelize per Kotlin Multiplatform

Prima di Kotlin 2.0, potevi utilizzare Parcelize creando alias delle annotazioni Parcelize con expect e 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

In Kotlin 2.0 e versioni successive, l'aliasing delle annotazioni che attivano i plug-in non è supportato. Per aggirare questo problema, fornisci una nuova annotazione Parcelize come parametro additionalAnnotation al plug-in.

// 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

Poiché l'interfaccia Parcel è disponibile solo su Android, Parcelize non genererà codice su altre piattaforme, quindi qualsiasi implementazione di actual può essere vuota. Inoltre, non è possibile utilizzare annotazioni che richiedono il riferimento alla classe Parcel, ad esempio @WriteWith, nel codice comune.

Funzionalità sperimentali

Serializzatore della classe di dati

Disponibile a partire da Kotlin 2.1.0.

L'annotazione DataClass consente di serializzare le classi di dati come se fossero annotate con Parcelize. Questa annotazione richiede l'attivazione di 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

Il costruttore principale e tutte le relative proprietà devono essere accessibili dalla classe Parcelable. Inoltre, tutte le proprietà del costruttore principale della classe di dati devono essere supportate da Parcelize. Se selezionati, i Custom Parcelers devono essere specificati nella classe Parcelable, non nella classe di dati. Se la classe di dati implementa Serializable contemporaneamente, l'annotazione @DataClass ha la priorità: android.os.Parcel.writeSerializable non verrà utilizzata.

Un caso d'uso pratico è la serializzazione di kotlin.Pair. Un altro esempio utile è la semplificazione del codice multipiattaforma: il codice comune potrebbe dichiarare il data layer come classi di dati, che il codice Android potrebbe poi aumentare con la logica di serializzazione, eliminando la necessità di annotazioni e alias di tipo specifici per Android nel codice comune.

// 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

Parametri non val o var nel costruttore principale

Disponibile a partire da Kotlin 2.1.0.

Per attivare questa funzionalità, aggiungi experimentalCodeGeneration=true agli argomenti del plug-in parcelize.

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

Questa funzionalità elimina la limitazione secondo cui gli argomenti del costruttore primario devono essere val o var. In questo modo si risolve un problema dell'utilizzo di parcelize con l'ereditarietà, che in precedenza richiedeva l'utilizzo delle proprietà 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)

Questi parametri possono essere utilizzati solo negli argomenti del costruttore della classe base. Non è consentito farvi riferimento nel corpo della classe.

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

Feedback

Se riscontri problemi con il kotlin-parcelizeplugin Gradle, puoi segnalare un bug.