Generator für Parcelable-Implementierungen

Das kotlin-parcelize-Plug-in bietet einen Generator für die Implementierung von Parcelable.

Wenn Sie Unterstützung für Parcelable hinzufügen möchten, fügen Sie das Gradle-Plug-in der Datei build.gradle Ihrer App hinzu:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Wenn Sie eine Klasse mit @Parcelize annotieren, wird automatisch eine Parcelable-Implementierung generiert, wie im folgenden Beispiel gezeigt:

// import kotlinx.parcelize.Parcelize

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

Für @Parcelize müssen alle serialisierten Properties im primären Konstruktor deklariert werden. Das Plug-in gibt für jede Property mit einem im Klassenrumpf deklarierten Backing Field eine Warnung aus. Außerdem können Sie @Parcelize nicht anwenden, wenn einige der primären Konstruktorparameter keine Attribute sind.

Wenn für Ihre Klasse eine komplexere Serialisierungslogik erforderlich ist, schreiben Sie sie in eine Companion-Klasse:

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

Unterstützte Typen

@Parcelize unterstützt eine Vielzahl von Typen:

  • Einfache Typen (und ihre boxed-Versionen)
  • Objekte und Enums
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Alle Serializable-Implementierungen (einschließlich Date) und Parcelable-Implementierungen
  • Sammlungen aller unterstützten Typen: List (zugeordnet zu ArrayList), Set (zugeordnet zu LinkedHashSet), Map (zugeordnet zu LinkedHashMap)
    • Außerdem gibt es eine Reihe konkreter Implementierungen: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Arrays aller unterstützten Typen
  • Nullable-Versionen aller unterstützten Typen

Benutzerdefiniert Parceler

Wenn Ihr Typ nicht direkt unterstützt wird, können Sie ein Parceler-Zuordnungsobjekt dafür schreiben.

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

Sie können externe Parceler mit den Annotationen @TypeParceler oder @WriteWith anwenden:

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

Daten aus Parcel erstellen

Im Java-Code können Sie direkt auf das Feld CREATOR zugreifen.

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

In Kotlin können Sie das Feld CREATOR nicht direkt verwenden. Verwenden Sie stattdessen kotlinx.parcelize.parcelableCreator.

// import kotlinx.parcelize.parcelableCreator

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

Properties von der Serialisierung ausschließen

Wenn Sie verhindern möchten, dass eine Property serialisiert wird, verwenden Sie die Annotation @IgnoredOnParcel. Es kann auch für Attribute im Hauptteil einer Klasse verwendet werden, um Warnungen zu unterdrücken, dass das Attribut nicht serialisiert wird. Für Konstruktorattribute, die mit @IgnoredOnParcel annotiert sind, muss ein Standardwert angegeben werden.

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

android.os.Parcel.writeValue zum Serialisieren eines Attributs verwenden

Sie können einen Typ mit @RawValue annotieren, damit Parcelize Parcel.writeValue für diese Eigenschaft verwendet.

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

Dies kann zur Laufzeit fehlschlagen, wenn der Wert der Property nicht von Android unterstützt wird.

Möglicherweise müssen Sie diese Annotation auch verwenden, wenn es keine andere Möglichkeit gibt, das Attribut zu serialisieren.

Parcelize mit versiegelten Klassen und versiegelten Schnittstellen

Für die Parcelize-Annotation muss die zu serialisierende Klasse nicht abstrakt sein. Diese Einschränkung gilt nicht für versiegelte Klassen. Wenn die Annotation @Parcelize für eine versiegelte Klasse verwendet wird, muss sie für die abgeleiteten Klassen nicht wiederholt werden.

@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

Parcelize für Kotlin Multiplatform einrichten

Vor Kotlin 2.0 konnten Sie Parcelize verwenden, indem Sie Parcelize-Annotationen mit expect und actual aliasieren:

// 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 und höher werden Alias-Annotationen, die Plug-ins auslösen, nicht unterstützt. Um dies zu umgehen, geben Sie stattdessen eine neue Parcelize-Annotation als additionalAnnotation-Parameter für das Plug-in an.

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

Da die Parcel-Schnittstelle nur auf Android verfügbar ist, generiert Parcelize auf anderen Plattformen keinen Code. Alle actual-Implementierungen können dort also leer sein. Außerdem ist es nicht möglich, eine Annotation zu verwenden, für die auf die Klasse Parcel verwiesen werden muss, z. B. @WriteWith, in gemeinsamem Code.

Experimentelle Funktionen

Bei Problemen können Sie uns gerne Feedback geben.

Serialisierung von Datenklassen

Verfügbar seit Kotlin 2.1.0.

Mit der Annotation DataClass können Datenklassen so serialisiert werden, als wären sie selbst mit Parcelize annotiert. Für diese Anmerkung ist die Einwilligung von kotlinx.parcelize.Experimental erforderlich.

// @file:OptIn(kotlinx.parcelize.Experimental::class)

data class C(val a: Int, val b: String)

@Parcelize
class P(val c: @DataClass C) : Parcelable

Der primäre Konstruktor und alle seine Eigenschaften müssen über die Klasse Parcelable zugänglich sein. Außerdem müssen alle primären Konstruktoreigenschaften der Datenklasse von Parcelize unterstützt werden. Benutzerdefinierte Parceler müssen, falls ausgewählt, in der Parcelable-Klasse und nicht in der Datenklasse angegeben werden. Wenn die Datenklasse gleichzeitig Serializable implementiert, hat die Annotation @DataClass Priorität: android.os.Parcel.writeSerializable wird nicht verwendet.

Ein praktischer Anwendungsfall dafür ist die Serialisierung von kotlin.Pair. Ein weiteres nützliches Beispiel ist die Vereinfachung von Multiplattform-Code: Im gemeinsamen Code könnte die Datenschicht als Datenklassen deklariert werden, die dann im Android-Code mit Serialisierungslogik erweitert werden. So sind keine Android-spezifischen Annotationen und Typ-Aliase im gemeinsamen Code mehr erforderlich.

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

Nicht-val- oder -var-Parameter im primären Konstruktor

Verfügbar seit Kotlin 2.1.0.

Um diese Funktion zu aktivieren, fügen Sie experimentalCodeGeneration=true den Argumenten des Parcelize-Plug-ins hinzu.

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

Diese Funktion hebt die Einschränkung auf, dass Primärkonstruktorargumente val oder var sein müssen. Dadurch wird ein Problem bei der Verwendung von „parcelize“ mit Vererbung behoben, für das zuvor open-Properties erforderlich waren.

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

Solche Parameter dürfen nur in Argumenten für den Konstruktor der Basisklasse verwendet werden. Es ist nicht zulässig, im Hauptteil der Klasse darauf zu verweisen.

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

Feedback

Wenn Sie Probleme mit dem kotlin-parcelize-Gradle-Plug-in haben, können Sie einen Fehler melden.