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 Parcelable dell'app:build.gradle

Groovy

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 siano dichiarate nel costruttore principale. Il plug-in invia un avviso per ogni proprietà con un campo di supporto dichiarato nel corpo della classe. Inoltre, non puoi applicare @Parcelize se alcuni dei parametri del costruttore principale non sono proprietà.

Se la classe richiede una logica di serializzazione più avanzata, scrivila in 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 le relative versioni con casella)
  • Oggetti ed enum
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface e FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Tutte le implementazioni di Serializable (incluso Date) e Parcelable
  • Collezioni di tutti i tipi supportati: List (mappata a ArrayList), Set (mappata a LinkedHashSet), Map (mappata a LinkedHashMap)
    • Inoltre, una serie di implementazioni concrete: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Array di tutti i tipi supportati
  • Versioni con valori null di tutti i tipi supportati

Parceler personalizzati

Se il tipo non è supportato direttamente, puoi scrivere un Parceler oggetto di mappatura per questo.

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

Saltare le proprietà dalla serializzazione

Se vuoi saltare il parcellamento di alcune proprietà, utilizza l'annotazione @IgnoredOnParcel. Può essere utilizzato anche per le proprietà all'interno del corpo di una classe per disattivare gli avvisi relativi alla mancata serializzazione della proprietà. 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
}

Utilizzare android.os.Parcel.writeValue per la serializzazione di 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.

La pacchettizzazione potrebbe anche richiedere di utilizzare questa annotazione quando non esiste un altro modo per serializzare la proprietà.

Suddividi in pacchetti con classi e interfacce sigillate

Per la pacchettizzazione, la partizione deve essere una classe non astratta. Questa limitazione non vale per le classi sigillate. Quando l'annotazione @Parcelize viene utilizzata in una classe sigillata, non deve essere ripetuta 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

Configura la Parcelize per la multipiattaforma Kotlin

Prima di Kotlin 2.0, potevi utilizzare Parcelize creando un alias per le 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, le annotazioni di alias che attivano i plug-in non sono supportate. Per aggirare il problema, fornisci al plug-in una nuova annotazione Parcelize come parametro additionalAnnotation.

// 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, l'applicazione Parcelize non genera codice su altre piattaforme, pertanto eventuali implementazioni actual possono essere vuote. Inoltre, non è possibile utilizzare nessuna annotazione che richiede fare riferimento alla classe Parcel, ad esempio @WriteWith, nel codice comune.

Funzionalità sperimentali

Serializzatore della classe di dati

Disponibile 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 sue proprietà devono essere accessibili dalla classe Parcelable. Inoltre, tutte le proprietà del costruttore principale della classe di dati devono essere supportate da Parcelize. I partizionatori personalizzati, se scelti, devono essere specificati nel 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 livello dati come classi di dati, che il codice Android potrebbe poi integrare 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 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 sugli argomenti principali del costruttore che devono essere val o var. In questo modo viene risolto uno dei problemi dell'utilizzo di parcelize con l'eredità, 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 constructor della classe di base. Non è consentito fare riferimento a questi contenuti nel corpo del corso.

@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 plug-in Gradle kotlin-parcelize, puoi segnalare un bug.