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