Generator implementacji rozwiązania Parcelable

Wtyczka kotlin-parcelize udostępnia generator implementacji Parcelable.

Aby uwzględnić obsługę języka Parcelable, dodaj wtyczkę Gradle do pliku build.gradle aplikacji:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Gdy dodasz adnotację do klasy za pomocą @Parcelize, zostanie automatycznie wygenerowana implementacja Parcelable, jak w tym przykładzie:

import kotlinx.parcelize.Parcelize

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

Funkcja @Parcelize wymaga, by wszystkie zserializowane właściwości były zadeklarowane w głównym konstruktorze. Wtyczka wyświetla ostrzeżenie w przypadku każdej właściwości z polem pomocniczym zadeklarowanym w ciele klasy. Nie możesz też zastosować @Parcelize, jeśli niektóre parametry konstruktora głównego nie są właściwościami.

Jeśli klasa wymaga bardziej zaawansowanej logiki serializacji, zaimplementuj ją w klasie towarzyszącej:

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

Obsługiwane typy

@Parcelize obsługuje wiele typów:

  • Typy podstawowe (i ich wersje pudełkowe)
  • Obiekty i typy enumeracji
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Wszystkie implementacje Serializable (w tym Date) i Parcelable
  • Zbiory wszystkich obsługiwanych typów: List (mapowane na ArrayList), Set (mapowane na LinkedHashSet), Map (mapowane na LinkedHashMap).
    • Kilka konkretnych implementacji: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • tablice wszystkich obsługiwanych typów;
  • wersje z możliwością przypisania wartości wszystkich obsługiwanych typów;

Niestandardowe Parceler

Jeśli Twój typ nie jest obsługiwany bezpośrednio, możesz napisać dla niego obiekt mapowania 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)
    }
}

Możesz stosować zewnętrzne funkcje parcelujące za pomocą adnotacji @TypeParceler lub @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

Tworzenie danych z Parcel

W kodzie Java możesz bezpośrednio uzyskać dostęp do pola CREATOR.

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

W języku Kotlin nie można bezpośrednio używać pola CREATOR. Zamiast tego używaj elementu kotlinx.parcelize.parcelableCreator.

import kotlinx.parcelize.parcelableCreator

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

Pomijanie właściwości podczas serializacji

Jeśli chcesz pominąć wyodrębnianie jakiejś właściwości, użyj adnotacji @IgnoredOnParcel. Można go też używać w przypadku właściwości w ciele klasy, aby wyciszyć ostrzeżenia o tym, że dana właściwość nie jest serializowana. Właściwości konstruktora opatrzone adnotacjami @IgnoredOnParcel muszą mieć domyślną wartość.

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

Używanie metody android.os.Parcel.writeValue do serializacji właściwości

Możesz dodać adnotację do typu za pomocą @RawValue, aby usługa Parcelize używała w przypadku tej usługi wartości Parcel.writeValue.

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

W czasie działania może to się nie udać, jeśli wartość właściwości nie jest natywnie obsługiwana przez Androida.

Parcelize może też wymagać użycia tej adnotacji, jeśli nie ma innego sposobu na serializację właściwości.

Zadbaj o komponent z zastosowanymi klasami i zamkniętymi interfejsami

Funkcja parcelize wymaga, aby klasa, którą ma być zapakowana, nie była abstrakcyjna. To ograniczenie nie dotyczy zajęć zamkniętych. Gdy adnotacja @Parcelize jest używana w przypadku zamkniętej klasy, nie trzeba jej powtarzać w przypadku klas pochodnych.

@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

Konfigurowanie Parcelize w przypadku Kotlin Multiplatform

Przed Kotlinem 2.0 można było używać Parcelize, zastępując adnotacje Parcelize za pomocą expect i actual:

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

W wersji 2.0 i nowszych języka Kotlin nie obsługuje adnotacji aliasów, które wywołują wtyczki. Aby to obejść, zamiast parametru additionalAnnotation podaj nową adnotację Parcelize jako parametr additionalAnnotation wtyczki.

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

Interfejs Parcel jest dostępny tylko na Androidzie, więc Parcelize nie wygeneruje kodu na innych platformach, a implementacje actual mogą być puste. Nie można też używać adnotacji, które wymagają odwołania do klasy Parcel, na przykład @WriteWith, w kodzie ogólnym.

Funkcje eksperymentalne

Serializator klasy danych

Dostępne od wersji Kotlin 2.1.0.

Adnotacja DataClass umożliwia serializowanie klas danych w taki sposób, jakby same były oznaczane adnotacjami Parcelize. Ta adnotacja wymaga włączenia 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

Główny konstruktor i wszystkie jego właściwości muszą być dostępne z klasy Parcelable. Dodatkowo wszystkie właściwości konstruktora głównego klasy danych muszą być obsługiwane przez Parcelize. Niestandardowe usługi dostawy, jeśli zostały wybrane, powinny być określone w klasie Parcelable, nie w klasie danych. Jeśli klasa danych implementuje tag Serializable w tym samym czasie, tag @DataClass ma pierwszeństwo: tag android.os.Parcel.writeSerializable nie będzie używany.

Przykładem praktycznego zastosowania jest serializacja kotlin.Pair. Innym przydatnym przykładem jest uproszczenie kodu wieloplatformowego: powszechny kod może zadeklarować warstwę danych jako klasy danych. W takim przypadku kod na Androida można uzupełnić logiką serializacji, eliminując potrzebę stosowania adnotacji związanych z Androidem i aliasów typów we wspólnym kodzie.

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

Parametry inne niż val lub var w konstrukcie głównym

Dostępne od wersji Kotlin 2.1.0.

Aby włączyć tę funkcję, dodaj experimentalCodeGeneration=true do argumentów wtyczki parcelize.

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

Ta funkcja znosi ograniczenie, które wymaga, aby argumenty konstruktora głównego były typu val lub var. Rozwiązuje to jeden problem z używaniem parcelize z dziedziczeniem, który wcześniej wymagał użycia właściwości 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)

Takich parametrów można używać tylko w argumentach w konstruktorze klasy podstawowej. Odwoływanie się do nich w treści zajęć jest niedozwolone.

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

Opinia

Jeśli napotkasz problemy z wtyczką Gradle kotlin-parcelize, możesz zgłosić błąd.