Trình tạo lượt triển khai Parcelable

Trình bổ trợ kotlin-parcelize cung cấp một trình tạo lượt triển khai Parcelable.

Để hỗ trợ Parcelable, hãy thêm trình bổ trợ Gradle vào tệp build.gradle của ứng dụng:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Khi bạn chú thích một lớp (class) bằng @Parcelize, phương thức triển khai Parcelable sẽ tự động được tạo, như trong ví dụ sau:

import kotlinx.parcelize.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable

@Parcelize đòi hỏi khai báo tất cả thuộc tính tuần tự trong hàm khởi tạo chính. Trình bổ trợ này cảnh báo về từng thuộc tính có một trường sao lưu được khai báo trong nội dung lớp. Ngoài ra, bạn không thể áp dụng @Parcelize nếu một số tham số của hàm khởi tạo chính không phải là thuộc tính.

Nếu lớp của bạn yêu cầu logic tuần tự hoá nâng cao hơn, hãy ghi lớp này vào trong lớp đồng hành (companion class):

@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
        }
    }
}

Các kiểu được hỗ trợ

@Parcelize hỗ trợ nhiều kiểu:

  • Kiểu nguyên bản (và các phiên bản đóng hộp)
  • Đối tượng và enum
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Tất cả cách triển khai Serializable (bao gồm cả Date) và Parcelable
  • Bộ sưu tập toàn bộ kiểu được hỗ trợ: List (được ánh xạ tới ArrayList), Set (được ánh xạ tới LinkedHashSet), Map (được ánh xạ tới LinkedHashMap)
    • Ngoài ra, có một số cách triển khai cụ thể: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Mảng (array) thuộc mọi kiểu được hỗ trợ
  • Phiên bản có tính chất rỗng của mọi kiểu được hỗ trợ

Parceler tuỳ chỉnh

Nếu kiểu của bạn không được hỗ trợ trực tiếp, bạn có thể viết một đối tượng ánh xạ Parceler cho kiểu đó.

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)
    }
}

Bạn có thể áp dụng các trình đóng gói (parceler) bên ngoài bằng chú thích @TypeParceler hoặc @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

Tạo dữ liệu trong Parcel

Trong mã Java, bạn có thể truy cập trực tiếp vào trường CREATOR.

class UserCreator {
    static User fromParcel(Parcel parcel) {
        return User.CREATOR.createFromParcel(parcel);
    }
}

Trong Kotlin, bạn không thể trực tiếp sử dụng trường CREATOR. Thay vào đó, hãy sử dụng kotlinx.parcelize.parcelableCreator.

import kotlinx.parcelize.parcelableCreator

fun userFromParcel(parcel: Parcel): User {
    return parcelableCreator<User>().createFromParcel(parcel)
}

Bỏ qua các thuộc tính trong quá trình chuyển đổi tuần tự

Nếu bạn muốn bỏ qua một số thuộc tính không được phân đoạn, hãy sử dụng chú thích @IgnoredOnParcel. Bạn cũng có thể dùng thuộc tính này trên các thuộc tính trong phần nội dung của một lớp để tắt tiếng các cảnh báo về việc thuộc tính không được chuyển đổi tuần tự. Các thuộc tính hàm khởi tạo được chú thích bằng @IgnoredOnParcel phải có giá trị mặc định.

@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
}

Sử dụng android.os.Parcel.writeValue để chuyển đổi tuần tự một thuộc tính

Bạn có thể chú thích một loại bằng @RawValue để Parcelize sử dụng Parcel.writeValue cho thuộc tính đó.

@Parcelize
class MyClass(val external: @RawValue ExternalClass): Parcelable

Thao tác này có thể không thành công trong thời gian chạy nếu giá trị của thuộc tính không được Android hỗ trợ sẵn.

Parcelize cũng có thể yêu cầu bạn sử dụng chú thích này khi không có cách nào khác để chuyển đổi tuần tự thuộc tính.

Đóng gói bằng các lớp kín và giao diện kín

Việc phân vùng yêu cầu phải phân vùng một lớp sao cho không phải là lớp trừu tượng. Hạn chế này không áp dụng cho các lớp kín. Khi chú thích @Parcelize được sử dụng trên một lớp kín, bạn không cần lặp lại chú thích này cho các lớp phái sinh.

@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

Thiết lập Parcelize cho Kotlin đa nền tảng

Trước Kotlin 2.0, bạn có thể sử dụng Parcelize bằng cách đặt bí danh cho các chú giải Parcelize bằng expectactual:

// 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

Trong Kotlin 2.0 trở lên, các chú giải đại diện kích hoạt trình bổ trợ sẽ không được hỗ trợ. Để khắc phục vấn đề này, hãy cung cấp chú thích Parcelize mới làm tham số additionalAnnotation cho trình bổ trợ.

// 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

Vì giao diện Parcel chỉ có trên Android, nên Parcelize sẽ không tạo bất kỳ mã nào trên các nền tảng khác, vì vậy, mọi hoạt động triển khai actual có thể bị trống. Bạn cũng không thể sử dụng bất kỳ chú giải nào yêu cầu tham chiếu đến lớp Parcel, chẳng hạn như @WriteWith, trong mã chung.

Các tính năng thử nghiệm

Trình chuyển đổi tuần tự lớp dữ liệu

Được cung cấp kể từ phiên bản Kotlin 2.1.0.

Chú giải DataClass cho phép chuyển đổi tuần tự các lớp dữ liệu như thể các lớp đó được chú thích bằng Parcelize. Chú thích này yêu cầu bạn chọn sử dụng 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

Bạn phải truy cập được hàm khởi tạo chính và tất cả thuộc tính của hàm đó từ lớp Parcelable. Ngoài ra, Parcelize phải hỗ trợ tất cả thuộc tính hàm khởi tạo chính của lớp dữ liệu. Bạn nên chỉ định Parceler tuỳ chỉnh (nếu được chọn) trên lớp Parcelable, chứ không phải trên lớp dữ liệu. Nếu lớp dữ liệu triển khai Serializable cùng một lúc, thì chú thích @DataClass sẽ được ưu tiên: android.os.Parcel.writeSerializable sẽ không được sử dụng.

Một trường hợp sử dụng thực tế cho việc này là chuyển đổi tuần tự kotlin.Pair. Một ví dụ hữu ích khác là đơn giản hoá mã đa nền tảng: mã chung có thể khai báo lớp dữ liệu dưới dạng lớp dữ liệu, sau đó mã Android có thể bổ sung logic chuyển đổi tuần tự, không cần chú thích dành riêng cho Android và bí danh loại trong mã chung.

// 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

Tham số không phải val hoặc var trong hàm khởi tạo chính

Được cung cấp kể từ phiên bản Kotlin 2.1.0.

Để bật tính năng này, hãy thêm experimentalCodeGeneration=true vào các đối số của trình bổ trợ phân vùng.

kotlin {
    compilerOptions {
        // ...
        freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:experimentalCodeGeneration=true")
    }
}

Tính năng này sẽ gỡ bỏ quy định hạn chế đối với đối số hàm khởi tạo chính phải là val hoặc var. Điều này giải quyết một vấn đề khó khăn khi sử dụng tính năng phân phối gói với tính năng kế thừa, trước đây yêu cầu sử dụng các thuộc tính 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)

Bạn chỉ được phép sử dụng các tham số như vậy trong đối số cho hàm khởi tạo lớp cơ sở. Bạn không được tham chiếu các thuộc tính này trong phần nội dung của lớp.

@Parcelize
class Derived(s: String): Base(s) { // allowed
    @IgnoredOnParcel
    val x: String = s // ERROR: not allowed.
    init {
        println(s) // ERROR: not allowed
    }
}

Phản hồi

Nếu gặp vấn đề với trình bổ trợ kotlin-parcelize cho Gradle, bạn có thể báo cáo lỗi.