הפלאגין 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, תוכלו לדווח על באג.