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 từ 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ể sử dụng trực tiếp 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 khỏi 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ỏi việc phân chia, hãy sử dụng chú thích @IgnoredOnParcel. Bạn cũng có thể sử dụng chú thích này trên các thuộc tính trong phần nội dung của một lớp để tắt 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 của 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ợ theo cách tự nhiê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.

Phân chia bằng các lớp kín và giao diện kín

Parcelize yêu cầu một lớp để phân chia 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 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 Multiplatform

Trước Kotlin 2.0, bạn có thể dùng Parcelize bằng cách đặt bí danh cho chú thích 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 bí danh kích hoạt trình bổ trợ không được hỗ trợ. Để khắc phục vấn đề này, hãy cung cấp một 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 mã trên các nền tảng khác, do đó, mọi hoạt động triển khai actual ở đó đều có thể trống. Bạn cũng không thể sử dụng bất kỳ chú thích 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ó từ Kotlin 2.1.0.

Chú giải DataClass cho phép bạn chuyển đổi các lớp dữ liệu thành chuỗi như thể chúng được chú giải 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

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

Một trường hợp sử dụng thực tế cho việc này là việc chuyển đổi kotlin.Pair thành dữ liệu nối tiếp. 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 các lớp dữ liệu, sau đó mã Android có thể tăng cường bằng logic chuyển đổi tuần tự, loại bỏ nhu cầu về chú thích và bí danh kiểu dành riêng cho Android 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ó từ 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ợ parcelize.

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

Tính năng này loại bỏ quy định hạn chế đối với các đối số của 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 đề khi sử dụng parcelize với tính 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 các đối số cho hàm khởi tạo lớp cơ sở. Bạn không được phép tham chiếu các đối tượng 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.