הפלאגין 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,CharSequenceDurationExceptionSize,SizeF,Bundle,IBinder,IInterface,FileDescriptorSparseArray,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, אתם יכולים לדווח על באג.