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

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

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

מגניב

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

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

@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 תומך במגוון רחב של סוגים:

  • סוגים פרימיטיביים (והגרסאות שלהם בתיבות)
  • אובייקטים וסוגי נתונים מנומריים
  • 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
  • מערכים של כל הסוגים הנתמכים
  • גרסאות שאפשר להגדיר כ-Null של כל הסוגים הנתמכים

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

יצירת נתונים ממגרש

בקוד 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. אפשר להשתמש בו גם במאפיינים בגוף של מחלקה כדי להשתיק אזהרות לגבי מאפיין שלא עובר סריאליזציה. מאפייני בנאי שמסומנים ב-@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

הגדרה של Parcelize ל-Kotlin Multiplatform

לפני 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, ולכן Parcelize לא ייצור קוד בפלטפורמות אחרות. כל הטמעה של 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. אם בוחרים באפשרות 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 בבונה הראשי

זמין מגרסה Kotlin 2.1.0.

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

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

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