افزونه kotlin-parcelize یک مولد پیادهسازی Parcelable ارائه میدهد.
برای پشتیبانی از Parcelable ، افزونه Gradle را به فایل build.gradle برنامه خود اضافه کنید:
گرووی
plugins { id 'kotlin-parcelize' }
کاتلین
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 از طیف گستردهای از انواع داده پشتیبانی میکند:
- انواع اولیه (و نسخههای جعبهای آنها)
- اشیاء و enumها
-
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 parcelerهای خارجی را اعمال کنید:
// 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
در کد جاوا، میتوانید مستقیماً به فیلد CREATOR دسترسی داشته باشید.
class UserCreator {
static User fromParcel(Parcel parcel) {
return User.CREATOR.createFromParcel(parcel);
}
}
در کاتلین، نمیتوانید مستقیماً از فیلد 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
اگر مقدار ویژگی به صورت پیشفرض توسط اندروید پشتیبانی نشود، ممکن است در زمان اجرا با شکست مواجه شود.
Parcelize همچنین ممکن است شما را ملزم به استفاده از این حاشیهنویسی کند، زمانی که هیچ راه دیگری برای سریالسازی ویژگی وجود ندارد.
بستهبندی با کلاسهای مهر و موم شده و رابطهای مهر و موم شده
Parcelize مستلزم آن است که کلاسی که parcelize را انجام میدهد، انتزاعی نباشد. این محدودیت برای کلاسهای sealed صدق نمیکند. وقتی از حاشیهنویسی @Parcelize روی یک کلاس sealed استفاده میشود، نیازی به تکرار آن برای کلاسهای مشتقشده نیست.
@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 برای چندسکویی کاتلین
قبل از کاتلین ۲.۰، میتوانستید با نام مستعار کردن حاشیهنویسیهای Parcelize با expect و actual از Parcelize استفاده کنید:
// 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
در کاتلین ۲.۰ و بالاتر، حاشیهنویسیهای مستعار که افزونهها را فعال میکنند پشتیبانی نمیشوند. برای جلوگیری از این مشکل، به جای آن، یک حاشیهنویسی 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 فقط در اندروید موجود است، Parcelize هیچ کدی را در پلتفرمهای دیگر تولید نمیکند، بنابراین هرگونه پیادهسازی actual در آنجا میتواند خالی باشد. همچنین استفاده از هرگونه حاشیهنویسی که نیاز به ارجاع به کلاس Parcel دارد، مثلاً @WriteWith ، در کد مشترک امکانپذیر نیست.
ویژگیهای آزمایشی
سریالساز کلاس داده
از کاتلین ۲.۱.۰ در دسترس است.
حاشیهنویسی 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
سازنده اصلی و تمام ویژگیهای آن باید از کلاس Parcelable قابل دسترسی باشند. علاوه بر این، تمام ویژگیهای سازنده اصلی کلاس داده باید توسط Parcelize پشتیبانی شوند. Parceler های سفارشی ، در صورت انتخاب، باید در کلاس Parcelable مشخص شوند، نه در کلاس داده. اگر کلاس داده همزمان Serializable پیادهسازی کند، حاشیهنویسی @DataClass اولویت دارد: android.os.Parcel.writeSerializable استفاده نخواهد شد.
یک مورد کاربردی برای این مورد، سریالسازی kotlin.Pair است. مثال مفید دیگر، سادهسازی کد چند پلتفرمی است: کد مشترک میتواند لایه داده را به عنوان کلاسهای داده تعریف کند، که کد اندروید میتواند آن را با منطق سریالسازی تقویت کند و نیاز به حاشیهنویسیهای مخصوص اندروید و نامهای مستعار نوع را در کد مشترک از بین ببرد.
// 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 در سازنده اصلی
از کاتلین ۲.۱.۰ در دسترس است.
برای فعال کردن این ویژگی 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 با مشکلی مواجه شدید، میتوانید یک باگ (bug) ثبت کنید .