Existe uma maneira conveniente de criar classes de dados Parcelable no Android com Kotlin?

116

Atualmente estou usando o excelente AutoParcel em meu projeto Java, o que facilita a criação de classes Parcelable.

Agora, Kotlin, que considero para meu próximo projeto, tem esse conceito de classes de dados, que geram automaticamente os métodos equals, hashCode e toString.

Existe uma maneira conveniente de tornar uma classe de dados Kotlin Parcelable de maneira conveniente (sem implementar os métodos manualmente)?

thalesmello
fonte
2
Você tentou kapt? blog.jetbrains.com/kotlin/2015/06/…
Sergey Mashkov
Você quer usar o AutoParcel com Kotlin? Eu pensei sobre isso, mas se houvesse uma maneira de ter classes de dados Parcelable embutidas na linguagem, seria lindo. Especialmente considerando que uma grande parte do uso do Kotlin virá de aplicativos Android.
thalesmello

Respostas:

187

Kotlin 1.1.4 está fora

O plugin de extensões do Android agora inclui um gerador automático de implementação Parcelable. Declare as propriedades serializadas em um construtor primário e adicione uma anotação @Parcelize, e os métodos writeToParcel () / createFromParcel () serão criados automaticamente:

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

Portanto, você precisa habilitá-los adicionando isto ao build.gradle do seu módulo :

apply plugin: 'org.jetbrains.kotlin.android.extensions'

android {
    androidExtensions {
        experimental = true
    }
}
Dhaval Jivani
fonte
2
Para quem quiser conferir: blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out
thalesmello
3
por que isso não é mais classe de dados. Este exemplo é apenas para mostrar que isso pode ser aplicado a qualquer classe kotlin genérica?
Nitin Jain
10
compilador reclama this calss implements Parcelable but does not provice CREATOR field. A sua resposta é suficiente (completa)?
murt de
1
@murt Você usou Parcelable com sucesso? Estou enfrentando um erro de compilação por causa de CREATOR
TOP
4
Você pode usar @SuppressLint("ParcelCreator")para se livrar do aviso de fiapos.
Masters holandeses
47

Você pode experimentar este plugin:

android-parcelable-intellij-plugin-kotlin

Ele ajuda a gerar o código padrão do Android Parcelable para a classe de dados de kotlin. E finalmente ficou assim:

data class Model(var test1: Int, var test2: Int): Parcelable {

    constructor(source: Parcel): this(source.readInt(), source.readInt())

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeInt(this.test1)
        dest?.writeInt(this.test2)
    }

    companion object {
        @JvmField final val CREATOR: Parcelable.Creator<Model> = object : Parcelable.Creator<Model> {
            override fun createFromParcel(source: Parcel): Model{
                return Model(source)
            }

            override fun newArray(size: Int): Array<Model?> {
                return arrayOfNulls(size)
            }
        }
    }
}
nekocode
fonte
19

Você já experimentou o PaperParcel ? É um processador de anotações que gera automaticamente o Parcelablecódigo padrão do Android para você.

Uso:

Anote sua classe de dados com @PaperParcel, implemente PaperParcelablee adicione uma instância estática JVM do gerado, CREATORpor exemplo:

@PaperParcel
data class Example(
  val test: Int,
  ...
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelExample.CREATOR
  }
}

Agora sua classe de dados é Parcelablee pode ser passada diretamente para um BundleouIntent

Editar: Atualizar com a API mais recente

Bradley Campbell
fonte
O IDE agora diz "A herança de classes de dados de outras classes é proibida". Acho que o PaperParcel não pode mais ser usado com classes de dados ...
Massimo
Eles nunca poderiam herdar de outras classes, mas podem implementar interfaces (por exemplo, PaperParcelable)
Bradley Campbell
1
@Bradley Campbell Isso me dá um erro nesta linha JvmField val CREATOR = PaperParcelExample.CREATOR não pode criar a classe
PaperParcelExample
19

Basta clicar na palavra-chave de dados de sua classe de dados kotlin, pressionar alt + Enter e selecionar a primeira opção dizendo "Add Parceable Implementation"

Manish Patiyal
fonte
2
Usei esse método, mas tem vários problemas. Às vezes, ele substitui um parcel.read...por TODO, se um campo não for val/var. Se você usar Listdentro de uma classe, sua implementação se torna um problema. Então me voltei para @Parcelizea resposta aceita.
CoolMind
16

A melhor maneira sem nenhum código clichê é o plugin Smuggler gradle. Tudo que você precisa é apenas implementar a interface AutoParcelable como

data class Person(val name:String, val age:Int): AutoParcelable

E isso é tudo. Funciona para classes fechadas também. Além disso, este plugin fornece validação de tempo de compilação para todas as classes AutoParcelable.

UPD 17.08.2017 Agora com o Kotlin 1.1.4 e o plugin de extensões do Kotlin Android, você pode usar @Parcelizeanotações. Neste caso, o exemplo acima será semelhante a:

@Parcelize class Person(val name:String, val age:Int): Parcelable

Não há necessidade de datamodificador. A maior desvantagem, por enquanto, é usar o plugin kotlin-android-extensions, que tem muitas outras funções que podem ser desnecessárias.

Stepango
fonte
6

Usando o Android Studio e o plug-in Kotlin , descobri uma maneira fácil de converter meus antigos Java Parcelables sem plug-ins extras (se tudo o que você deseja é transformar uma dataclasse totalmente nova em um Parcelable, pule para o quarto trecho de código).

Digamos que você tenha uma Personclasse com toda a estrutura Parcelablepadrão:

public class Person implements Parcelable{
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    protected Person(Parcel in) {
        firstName = in.readString();
        lastName = in.readString();
        age = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(firstName);
        dest.writeString(lastName);
        dest.writeInt(age);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
}

Comece eliminando a Parcelableimplementação, deixando um objeto Java básico e simples (as propriedades devem ser finais e definidas pelo construtor):

public class Person {
    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
}

Em seguida, deixe a Code > Convert Java file to Kotlin Fileopção fazer sua mágica:

class Person(val firstName: String, val lastName: String, val age: Int)

Converta isso em uma dataclasse:

data class Person(val firstName: String, val lastName: String, val age: Int)

E, finalmente, vamos transformar isso em um Parcelablenovamente. Passe o mouse sobre o nome da classe e o Android Studio fornecerá a opção de Add Parcelable Implementation. O resultado deve ser assim:

data class Person(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(firstName)
        parcel.writeString(lastName)
        parcel.writeInt(age)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            return Person(parcel)
        }

        override fun newArray(size: Int): Array<Person?> {
            return arrayOfNulls(size)
        }
    }
}

Como você pode ver, a Parcelableimplementação é algum código gerado automaticamente anexado à sua datadefinição de classe.

Notas:

  1. Tentar converter um Java Parcelable diretamente em Kotlin não produzirá o mesmo resultado com a versão atual do plug-in Kotlin ( 1.1.3).
  2. Tive que remover algumas chaves extras Parcelableintroduzidas pelo gerador de código atual . Deve ser um bug menor.

Espero que esta dica funcione para você tão bem quanto funcionou para mim.

argenkiwi
fonte
4

Vou deixar minha maneira de fazer caso possa ajudar alguém.

O que eu faço é ter um genérico Parcelable

interface DefaultParcelable : Parcelable {
    override fun describeContents(): Int = 0

    companion object {
        fun <T> generateCreator(create: (source: Parcel) -> T): Parcelable.Creator<T> = object: Parcelable.Creator<T> {
            override fun createFromParcel(source: Parcel): T = create(source)

            override fun newArray(size: Int): Array<out T>? = newArray(size)
        }

    }
}

inline fun <reified T> Parcel.read(): T = readValue(T::class.javaClass.classLoader) as T
fun Parcel.write(vararg values: Any?) = values.forEach { writeValue(it) }

E então eu crio pacotes como este:

data class MyParcelable(val data1: Data1, val data2: Data2) : DefaultParcelable {
    override fun writeToParcel(dest: Parcel, flags: Int) { dest.write(data1, data2) }
    companion object { @JvmField final val CREATOR = DefaultParcelable.generateCreator { MyParcelable(it.read(), it.read()) } }
}

O que me livra dessa substituição clichê.

gmemario
fonte
4
  • Use @Parcelize anotação no topo de sua classe Model / Data
  • Use a versão mais recente do Kotlin
  • Use a versão mais recente das extensões Kotlin Android no módulo do seu aplicativo

Exemplo:

@Parcelize
data class Item(
    var imageUrl: String,
    var title: String,
    var description: Category
) : Parcelable
Manoj Bhadane
fonte
3

Infelizmente, não há como no Kotlin colocar um campo real em uma interface, então você não pode herdá-lo de um adaptador de interface gratuitamente: data class Par : MyParcelable

Você pode olhar para delegação, mas não ajuda com campos, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Portanto, a única opção que vejo é uma função de tecido Parcelable.Creator, o que é meio óbvio.

voddan
fonte
2

eu prefiro apenas usar o https://github.com/johncarl81/parceler lib com

@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)
Jan Rabe
fonte
Isso dá o erro "Classe 'MyClass' não é abstrata e não implementa membro abstrato public abstract fun writeToParcel (dest: Parcel !, flags: Int): Unidade definida em android.os.Parcelable
PhillyTheThrilly
2

Você pode fazer isso usando @Parcelizeanotações. É um gerador automático de implementação Parcelable.

Primeiro, você precisa habilitá-los adicionando isto ao build.gradle do seu módulo:

apply plugin: 'org.jetbrains.kotlin.android.extensions'

Declare as propriedades serializadas em um construtor primário e adicione uma @Parcelizeanotação, e os métodos writeToParcel()/ createFromParcel()serão criados automaticamente:

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

Você NÃO precisa adicionar um bloco experimental = trueinterno androidExtensions.

Malwinder Singh
fonte
1

O Kotlin tornou todo o processo de Parcelização no Android muito fácil com sua anotação @Parcel.

Fazer isso

Etapa 1. Adicionar extensões Kotlin ao gradle do módulo de aplicativo

Etapa 2. Adicione experimental = true, pois esse recurso ainda está em experimentação no gradle.

androidExtensions {experimental = true}

Etapa 3. Anote a classe de dados com @Parcel

Aqui está um exemplo simples de uso do @Parcel

Ramakrishna Joshi
fonte