مولد پیاده سازی قابل حمل

افزونه 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 (نگاشت‌شده به ArrayListSet (نگاشت‌شده به LinkedHashSetMap (نگاشت‌شده به 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) ثبت کنید .