מחולל הטמעה שניתן למנות

הפלאגין kotlin-parcelize מספק גנרטור להטמעה של Parcelable.

כדי לכלול תמיכה ב-Parcelable, מוסיפים את הפלאגין של Gradle לקובץ build.gradle של האפליקציה:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

כשאתם מוסיפים הערה לכיתה באמצעות @Parcelize, המערכת יוצרת באופן אוטומטי הטמעה של Parcelable, כפי שמוצג בדוגמה הבאה:

import kotlinx.parcelize.Parcelize

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

כדי להצהיר על @Parcelize ב-constructor הראשי, צריך להצהיר על כל המאפיינים שעברו סריאליזציה. הפלאגין מנפיק אזהרה לגבי כל נכס עם שדה תמיכה שמוצהר בגוף הכיתה. בנוסף, אי אפשר להחיל את @Parcelize אם חלק מהפרמטרים הראשיים של ה-constructor הם לא מאפיינים.

אם בכיתה שלכם נדרשת לוגיקה מתקדמת יותר של שרשור, כתבו אותה בכיתה נלווית:

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

סוגי הקבצים הנתמכים

@Parcelize תומך במגוון רחב של סוגים:

  • סוגי נתונים פרימיטיביים (וגרסאות ה-boxed שלהם)
  • אובייקטים וטיפוסים בני מנייה (enums)
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • כל הטמעות Serializable (כולל Date) ו-Parcelable
  • אוספים של כל הסוגים הנתמכים: List (הממופה ל-ArrayList), Set (הממופה ל-LinkedHashSet), Map (הממופה ל-LinkedHashMap)
    • וגם מספר הטמעות קונקרטיות: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • מערכי נתונים מכל הסוגים הנתמכים
  • גרסאות איפוס של כל הסוגים הנתמכים

Parceler בהתאמה אישית

אם הסוג לא נתמך ישירות, אפשר לכתוב אובייקט מיפוי 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)
    }
}

אפשר להחיל מגרשים חיצוניים באמצעות הסימונים @TypeParceler או @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

יצירת נתונים מ-Parcel

בקוד Java, אפשר לגשת ישירות לשדה CREATOR.

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

ב-Kotlin אי אפשר להשתמש ישירות בשדה CREATOR. במקום זאת, אתם צריכים להשתמש ב-kotlinx.parcelize.parcelableCreator.

import kotlinx.parcelize.parcelableCreator

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

דילוג על מאפיינים מהסדרה

אם רוצים לדלג על חלוקה של נכס מסוים, אפשר להשתמש בהערה @IgnoredOnParcel. אפשר להשתמש בו גם במאפיינים בגוף הכיתה כדי להשתיק אזהרות על כך שהמאפיין לא עובר שרשור. למאפייני המבנה המבוסס על ברירת מחדל (constructor) שמסומנים ב-@IgnoredOnParcel חייב להיות ערך ברירת מחדל.

@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 לסריאליזציה של נכס

אפשר להוסיף הערות לסוג @RawValue כדי להפוך את השימוש ב-Parcelize ל-Parcel.writeValue לנכס הזה.

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

יכול להיות שהפעולה הזו תיכשל במהלך זמן הריצה אם הערך של המאפיין לא נתמך באופן מקורי על ידי Android.

יכול להיות שתצטרכו להשתמש בהערה הזו גם ב-Parcelize כשאין דרך אחרת לסריאליזציה של הנכס.

חלוקה לחבילות באמצעות מחלקות אטומות וממשקים אטומים

כדי לפצל את הכיתה, היא לא יכולה להיות מופשטת. ההגבלה הזו לא חלה על כיתות חתולות. כשמשתמשים בהערה @Parcelize בכיתה אטומה, אין צורך לחזור עליה בכיתות המבוססות עליה.

@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

הגדרת חבילה לריבוי פלטפורמות של Kotlin

לפני Kotlin 2.0, אפשר היה להשתמש ב-Parcelize על ידי שיוך כתובות חלופיות להערות Parcelize באמצעות expect ו-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

ב-Kotlin 2.0 ואילך, אין תמיכה בהערות חלופיות להפעלת יישומי פלאגין. כדי לעקוף את הבעיה הזו, צריך לספק הערה חדשה של Parcelize בתור הפרמטר 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

מכיוון שהממשק Parcel זמין רק ב-Android, הקוד לא יופיע בפלטפורמות אחרות, כך שכל הטמעות actual יכולות להיות ריקות. בנוסף, אי אפשר להשתמש בקוד משותף בהערות שמחייבות הפניה לכיתה Parcel, למשל @WriteWith.

תכונות ניסיוניות

מבצע סריאליזציה לסיווג נתונים

זמין החל מ-Kotlin 2.1.0.

ההערה DataClass מאפשרת לסדר סדרתי של כיתות נתונים כאילו הן עצמן סומנו ב-Parcelize. כדי להשתמש בהערה הזו צריך להביע הסכמה 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

ה-constructor הראשי וכל המאפיינים שלו צריכים להיות נגישים מהמחלקה Parcelable. בנוסף, Parcelize חייב לתמוך בכל המאפיינים הראשיים של ה-constructor של סוג הנתונים. אם בוחרים באפשרות Custom Parcelers, צריך לציין אותה בכיתה Parcelable, לא בכיתה של הנתונים. אם סיווג הנתונים מטמיע את Serializable בו-זמנית, ההערה @DataClass מקבלת עדיפות: לא נעשה שימוש ב-android.os.Parcel.writeSerializable.

תרחיש לדוגמה לשימוש מעשי בכך הוא סריאליזציה של kotlin.Pair. דוגמה שימושית נוספת היא פישוט של קוד מרובה-פלטפורמות: קוד נפוץ יכול להצהיר על שכבת הנתונים כסיווג נתונים, שאותו קוד Android יכול להרחיב באמצעות לוגיקת סריאליזציה, וכך מבטל את הצורך בהערות ספציפיות ל-Android ובכינויים של סוגים בקוד משותף.

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

פרמטרים שאינם val או var ב-constructor הראשי

האפשרות הזו זמינה מ-Kotlin 2.1.0.

כדי להפעיל את התכונה הזו, מוסיפים את הערך experimentalCodeGeneration=true לארגומנטים של הפלאגין parcelize.

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

התכונה הזו מבטלת את ההגבלה על הארגומנטים הראשיים של ה-constructor, כך שהם יכולים להיות val או var. כך פותרים בעיה אחת בשימוש ב-parcelize עם ירושה, שבעבר היה צורך להשתמש במאפיינים 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)

מותר להשתמש בפרמטרים כאלה רק בארגומנטים למבנה של הכיתה הבסיסית. אסור להפנות אליהם בגוף הכיתה.

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

משוב

אם תיתקלו בבעיות בפלאגין kotlin-parcelize Gradle, תוכלו לדווח על באג.