Générateur d'implémentation Parcelable

Le plug-in kotlin-parcelize fournit un générateur d'implémentation Parcelable.

Pour inclure la compatibilité avec Parcelable, ajoutez le plug-in Gradle au fichier build.gradle de votre application :

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Lorsque vous annotez une classe avec @Parcelize, une implémentation Parcelable est automatiquement générée, comme illustré dans l'exemple suivant :

import kotlinx.parcelize.Parcelize

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

@Parcelize nécessite que toutes les propriétés sérialisées soient déclarées dans le constructeur principal. Le plug-in émet un avertissement sur chaque propriété avec un champ de support déclaré dans le corps de la classe. De plus, vous ne pouvez pas appliquer @Parcelize si certains paramètres du constructeur principal ne sont pas des propriétés.

Si votre classe nécessite une logique de sérialisation plus avancée, écrivez-la dans une classe compagnon :

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

Types acceptés

@Parcelize accepte un large éventail de types :

  • Types primitifs (et leurs versions encapsulées)
  • Objets et énumérations
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Toutes les implémentations Serializable (y compris Date) et Parcelable
  • Les collections de tous les types compatibles : List (mappé sur ArrayList), Set (mappé sur LinkedHashSet), Map (mappé sur LinkedHashMap)
    • Plusieurs implémentations concrètes sont également possibles : ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Tableaux de tous les types compatibles
  • Versions nullable de tous les types compatibles

Parceler personnalisés

Si votre type n'est pas directement pris en charge, vous pouvez écrire un objet de mappage 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)
    }
}

Vous pouvez appliquer des Parceler externes à l'aide d'annotations @TypeParceler ou @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

Créer des données à partir d'un lot

Dans le code Java, vous pouvez accéder directement au champ CREATOR.

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

En Kotlin, vous ne pouvez pas utiliser directement le champ CREATOR. Utilisez plutôt kotlinx.parcelize.parcelableCreator.

import kotlinx.parcelize.parcelableCreator

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

Ignorer les propriétés de la sérialisation

Si vous souhaitez que certaines propriétés ne soient pas morcelées, utilisez l'annotation @IgnoredOnParcel. Il peut également être utilisé sur les propriétés du corps d'une classe pour désactiver les avertissements concernant la non-sérialisation de la propriété. Les propriétés du constructeur annotées avec @IgnoredOnParcel doivent avoir une valeur par défaut.

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

Utiliser android.os.Parcel.writeValue pour sérialiser une propriété

Vous pouvez annoter un type avec @RawValue pour que Parcelize utilise Parcel.writeValue pour cette propriété.

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

Cette opération peut échouer au moment de l'exécution si la valeur de la propriété n'est pas nativement compatible avec Android.

Parcelize peut également vous demander d'utiliser cette annotation lorsqu'il n'existe aucun autre moyen de sérialiser la propriété.

Parceller avec des classes et des interfaces scellées

Parcelize nécessite qu'une classe à parcelliser ne soit pas abstraite. Cette limitation ne s'applique pas aux classes scellées. Lorsque l'annotation @Parcelize est utilisée sur une classe scellée, elle n'a pas besoin d'être répétée pour les classes dérivées.

@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

Configurer Parcelize pour la multiplateforme Kotlin

Avant la version Kotlin 2.0, vous pouviez utiliser Parcelize en créant un alias pour les annotations Parcelize avec expect et 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

Dans Kotlin 2.0 et versions ultérieures, la création d'alias pour les annotations qui déclenchent des plug-ins n'est pas prise en charge. Pour contourner ce problème, fournissez plutôt une nouvelle annotation Parcelize en tant que paramètre additionalAnnotation au 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

Étant donné que l'interface Parcel n'est disponible que sur Android, Parcelize ne génère aucun code sur d'autres plates-formes. Par conséquent, toutes les implémentations actual peuvent être vides. Il n'est pas non plus possible d'utiliser une annotation qui nécessite de faire référence à la classe Parcel, par exemple @WriteWith, dans le code commun.

Fonctionnalités expérimentales

Sérialiseur de classe de données

Cette fonctionnalité est disponible depuis la version 2.1.0 de Kotlin.

L'annotation DataClass permet de sérialiser les classes de données comme si elles étaient elles-mêmes annotées avec Parcelize. Cette annotation nécessite l'activation 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

Le constructeur principal et toutes ses propriétés doivent être accessibles depuis la classe Parcelable. De plus, toutes les propriétés du constructeur principal de la classe de données doivent être compatibles avec Parcelize. Les parcelleurs personnalisés, le cas échéant, doivent être spécifiés sur la classe Parcelable, et non sur la classe de données. Si la classe de données implémente Serializable en même temps, l'annotation @DataClass est prioritaire : android.os.Parcel.writeSerializable n'est pas utilisé.

Un cas d'utilisation pratique est la sérialisation de kotlin.Pair. Un autre exemple utile est la simplification du code multiplate-forme : le code commun peut déclarer la couche de données en tant que classes de données, que le code Android peut ensuite enrichir avec une logique de sérialisation, ce qui élimine le besoin d'annotations et d'alias de type spécifiques à Android dans le code commun.

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

Paramètres non val ou var dans le constructeur principal

Disponible depuis Kotlin 2.1.0.

Pour activer cette fonctionnalité, ajoutez experimentalCodeGeneration=true aux arguments du plug-in parcelize.

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

Cette fonctionnalité lève la restriction selon laquelle les arguments du constructeur principal doivent être val ou var. Cela résout un problème lié à l'utilisation de parcelize avec l'héritage, qui nécessitait auparavant d'utiliser des propriétés 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)

Ces paramètres ne sont autorisés que dans les arguments du constructeur de la classe de base. Il est interdit de les référencer dans le corps de la classe.

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

Commentaires

Si vous rencontrez des problèmes avec le plug-in Gradle kotlin-parcelize, vous veuillez signaler un bug.