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

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

תרחיש לדוגמה לשימוש מעשי בכך הוא סריאליזציה של kotlin.Pair. דוגמה שימושית נוספת היא פישוט של קוד לפלטפורמות מרובות: קוד משותף יכול להצהיר על שכבת הנתונים ככיתות נתונים, שקוד Android יכול להוסיף אליהן לוגיקה של שרשור (serialization), וכך לבטל את הצורך בהערות ספציפיות ל-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. כך ניתן לפתור בעיה אחת של שימוש בהקצאה עם ירושה, דבר שדרש קודם לכן באמצעות נכסי 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, תוכלו לדווח על באג.