تولیدکننده پیاده‌سازی Parcelable

افزایه 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 از طیف گسترده‌ای از انواع پشتیبانی می‌کند:

  • انواع اولیه (و نسخه‌های جعبه‌ای آن‌ها)
  • اشیا و شمارش‌ها
  • 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 در کلاس مهروموم‌شده استفاده می‌شود، لازم نیست برای کلاس‌های مشتق‌شده تکرار شود.

@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 چندپلاتفرمی

قبل‌از Kotlin 2.0، می‌توانید با نام مستعار دادن به گزارمان‌های 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

در Kotlin نسخه ۲.۰ و بالاتر، نام مستعار حاشیه‌نویسی‌هایی که افزایه‌ها را راه‌اندازی می‌کنند پشتیبانی نمی‌شود. برای دور زدن این محدودیت، به‌جای آن، یک 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

سازنده اصلی و همه دارایی‌های آن باید از کلاس 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 مشکلی داشتید، می‌توانید گزارش اشکال ثبت کنید.