Gerador de implementação de Parcelable

O plug-in kotlin-parcelize (link em inglês) fornece um gerador de implementação de Parcelable.

Para incluir suporte à interface Parcelable, adicione o plug-in do Gradle ao arquivo build.gradle do app:

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

Quando você insere uma anotação com @Parcelize, uma implementação de Parcelable é gerada automaticamente, como mostrado no exemplo a seguir.

// import kotlinx.parcelize.Parcelize

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

@Parcelize requer que todas as propriedades serializadas sejam declaradas no construtor principal. O plug-in emite um aviso em cada propriedade com um campo de backup declarado no corpo da classe. Além disso, não será possível aplicar @Parcelize se alguns dos parâmetros do construtor principal não forem propriedades.

Caso sua classe exija uma lógica de serialização mais avançada, escreva-a dentro de uma classe de acompanhamento:

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

Tipos com suporte

@Parcelize aceita uma grande variedade de tipos:

  • Tipos primitivos (e suas versões em caixa)
  • Objetos e enumerações
  • String, CharSequence
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • Todas as implementações de Serializable (incluindo Date) e Parcelable
  • Coleções de todos os tipos com suporte: List (mapeado para ArrayList), Set (mapeado para LinkedHashSet), Map (mapeado para LinkedHashMap)
    • Além disso, algumas implementações concretas: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • Matrizes de todos os tipos com suporte
  • Versões anuláveis de todos os tipos com suporte

Parcelers personalizados

Se o tipo não tiver suporte direto, crie um objeto de mapeamento Parceler para ele.

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

É possível aplicar parcelers externos usando anotações @TypeParceler ou @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

Criar dados do Parcel

No código Java, é possível acessar o campo CREATOR diretamente.

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

Em Kotlin, não é possível usar o campo CREATOR diretamente. Em vez disso, use kotlinx.parcelize.parcelableCreator.

// import kotlinx.parcelize.parcelableCreator

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

Ignorar propriedades da serialização

Se você quiser pular a parcelização de uma propriedade, use a anotação @IgnoredOnParcel. Também pode ser usado em propriedades dentro do corpo de uma classe para silenciar avisos sobre a propriedade não ser serializada. As propriedades do construtor anotadas com @IgnoredOnParcel precisam ter um valor padrão.

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

Use android.os.Parcel.writeValue para serializar uma propriedade

Você pode anotar um tipo com @RawValue para que o Parcelize use Parcel.writeValue nessa propriedade.

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

Isso pode falhar no tempo de execução se o valor da propriedade não for compatível nativamente com o Android.

O Parcelize também pode exigir que você use essa anotação quando não houver outra maneira de serializar a propriedade.

Parcelize com classes e interfaces seladas

O Parcelize exige que uma classe a ser parcelada não seja abstrata. Essa limitação não se aplica a classes sealed. Quando a anotação @Parcelize é usada em uma classe selada, ela não precisa ser repetida para as classes derivadas.

@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

Configurar o Parcelize para Kotlin Multiplatform

Antes do Kotlin 2.0, era possível usar o Parcelize criando um alias para as anotações com expect e 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

No Kotlin 2.0 e versões mais recentes, não há suporte para anotações de alias que acionam plug-ins. Para evitar isso, forneça uma nova anotação Parcelize como o parâmetro additionalAnnotation ao plug-in.

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

Como a interface Parcel só está disponível no Android, o Parcelize não gera código em outras plataformas. Portanto, qualquer implementação de actual pode estar vazia. Também não é possível usar nenhuma anotação que exija referenciar a classe Parcel, por exemplo @WriteWith, em código comum.

Recursos experimentais

Serializador de classe de dados

Disponível desde o Kotlin 2.1.0.

A anotação DataClass permite serializar classes de dados como se elas próprias tivessem a anotação Parcelize. Essa anotação requer a ativação da 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

O construtor principal e todas as propriedades dele precisam estar acessíveis na classe Parcelable. Além disso, todas as propriedades do construtor principal da classe de dados precisam ser compatíveis com Parcelize. Os Parcelers personalizados, se escolhidos, precisam ser especificados na classe Parcelable, não na classe de dados. Se a classe de dados implementar Serializable ao mesmo tempo, a anotação @DataClass terá prioridade: android.os.Parcel.writeSerializable não será usado.

Um caso de uso prático disso é a serialização de kotlin.Pair. Outro exemplo útil é a simplificação do código multiplataforma: o código comum pode declarar a camada de dados como classes de dados, que o código do Android pode aumentar com a lógica de serialização, removendo a necessidade de anotações e aliases de tipo específicos do Android no código comum.

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

Parâmetros não val ou var no construtor principal

Disponível desde o Kotlin 2.1.0.

Para ativar esse recurso, adicione experimentalCodeGeneration=true aos argumentos do plug-in parcelize.

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

Esse recurso elimina a restrição de que os argumentos do construtor principal precisam ser val ou var. Isso resolve um problema de usar parcelize com herança, que antes exigia o uso de propriedades 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)

Esses parâmetros só podem ser usados em argumentos para o construtor da classe de base. Não é permitido fazer referência a eles no corpo da aula.

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

Feedback

Se você encontrar problemas com o plug-in do Gradle kotlin-parcelize, registre um bug.