Existe-t-il un moyen pratique de créer des classes de données parcellables dans Android avec Kotlin?

117

J'utilise actuellement l'excellent AutoParcel dans mon projet Java, qui facilite la création de classes Parcelable.

Maintenant, Kotlin, que je considère pour mon prochain projet, a ce concept de classes de données, qui génèrent automatiquement les méthodes equals, hashCode et toString.

Existe-t-il un moyen pratique de rendre une classe de données Kotlin Parcelable de manière pratique (sans implémenter les méthodes manuellement)?

thalesmello
la source
2
Avez-vous essayé kapt? blog.jetbrains.com/kotlin/2015/06/…
Sergey Mashkov
Vous voulez utiliser AutoParcel avec Kotlin? J'y ai pensé, mais s'il y avait un moyen d'avoir des classes de données Parcelable intégrées dans le langage, ce serait magnifique. Étant donné qu'une grande partie de l'utilisation de Kotlin proviendra des applications Android.
thalesmello

Réponses:

188

Kotlin 1.1.4 est sorti

Le plugin Android Extensions comprend désormais un générateur d'implémentation Parcelable automatique. Déclarez les propriétés sérialisées dans un constructeur principal et ajoutez une annotation @Parcelize, et les méthodes writeToParcel () / createFromParcel () seront créées automatiquement:

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

Vous devez donc les activer en ajoutant ceci à build.gradle de votre module :

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

android {
    androidExtensions {
        experimental = true
    }
}
Dhaval Jivani
la source
2
Pour ceux qui veulent le vérifier: blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out
thalesmello
3
pourquoi n'est-ce plus une classe de données. Cet exemple est-il juste pour montrer que cela pourrait être appliqué à n'importe quelle classe générique de kotlin?
Nitin Jain
10
se plaint du compilateur this calss implements Parcelable but does not provice CREATOR field. Votre réponse est-elle suffisante (complète)?
murt
1
@murt Avez-vous réussi à utiliser Parcelable? Je suis confronté à l'erreur de compilation à cause de CREATOR
TOP
4
Vous pouvez utiliser @SuppressLint("ParcelCreator")pour vous débarrasser de l'avertissement de peluche.
Dutch Masters
47

Vous pouvez essayer ce plugin:

android-parcelable-intellij-plugin-kotlin

Il vous aide à générer un code standard Android Parcelable pour la classe de données de kotlin. Et cela ressemble finalement à ceci:

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
la source
20

Cliquez simplement sur le mot-clé data de votre classe de données kotlin, puis appuyez sur Alt + Entrée, sélectionnez la première option disant "Add Parceable Implementation"

Manish Patiyal
la source
2
J'ai utilisé cette méthode, mais elle pose plusieurs problèmes. Parfois, il remplace un parcel.read...par TODO, si un champ ne l'est pas val/var. Si vous utilisez à l' Listintérieur d'une classe, votre implémentation devient un problème. Alors je me suis tourné vers @Parcelizela réponse acceptée.
CoolMind
19

Avez-vous essayé PaperParcel ? C'est un processeur d'annotation qui génère automatiquement le Parcelablecode standard d' Android pour vous.

Usage:

Annotez votre classe de données avec @PaperParcel, implémentez PaperParcelableet ajoutez une instance statique JVM du fichier généré, CREATORpar exemple:

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

Maintenant, votre classe de données est Parcelableet peut être transmise directement à un BundleouIntent

Edit: mise à jour avec la dernière API

Bradley Campbell
la source
L'EDI dit maintenant que "l'héritage des classes de données d'autres classes est interdit". Je pense que PaperParcel ne peut plus être utilisé avec des classes de données ...
Massimo
Ils ne pourraient jamais hériter d'autres classes, mais ils peuvent implémenter des interfaces (par exemple PaperParcelable)
Bradley Campbell
1
@Bradley Campbell Cela me donne une erreur dans cette ligne JvmField val CREATOR = PaperParcelExample.CREATOR ne peut pas créer la classe
PaperParcelExample
16

Le meilleur moyen sans code standard du tout est le plugin Smuggler Gradle. Tout ce dont vous avez besoin est simplement d'implémenter une interface AutoParcelable comme

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

Et c'est tout. Fonctionne également pour les classes scellées. De plus, ce plugin fournit une validation au moment de la compilation pour toutes les classes AutoParcelable.

UPD 17.08.2017 Maintenant, avec le plugin d'extensions Kotlin 1.1.4 et Kotlin Android, vous pouvez utiliser l' @Parcelizeannotation. Dans ce cas, l'exemple ci-dessus ressemblera à:

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

Pas besoin de datamodificateur. Le plus gros inconvénient, pour le moment, est l'utilisation du plugin kotlin-android-extensions qui a beaucoup d'autres fonctions qui pourraient être inutiles.

Stepango
la source
6

En utilisant Android Studio et le plugin Kotlin , j'ai trouvé un moyen simple de convertir mes anciens Java Parcelables sans plugins supplémentaires (si tout ce que vous voulez est de transformer une toute nouvelle dataclasse en un Parcelable, passez au 4ème extrait de code).

Disons que vous avez une Personclasse avec toute la Parcelableplaque de la chaudière:

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

Commencez par supprimer l' Parcelableimplémentation, en laissant un vieil objet Java pur et simple (les propriétés doivent être définitives et définies par le constructeur):

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

Ensuite, laissez l' Code > Convert Java file to Kotlin Fileoption faire sa magie:

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

Convertissez ceci en une dataclasse:

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

Et enfin, transformons cela en un Parcelablenouveau. Passez la souris sur le nom de la classe et Android Studio devrait vous donner la possibilité de Add Parcelable Implementation. Le résultat devrait ressembler à ceci:

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

Comme vous pouvez le voir, l' Parcelableimplémentation est un code généré automatiquement ajouté à votre datadéfinition de classe.

Remarques:

  1. Essayer de convertir un Java Parcelable directement en Kotlin ne produira pas le même résultat avec la version actuelle du plugin Kotlin ( 1.1.3).
  2. J'ai dû supprimer quelques accolades supplémentaires que le Parcelablegénérateur de code actuel introduit. Doit être un bug mineur.

J'espère que cette astuce fonctionnera pour vous aussi bien que pour moi.

argenkiwi
la source
4

Je quitterai ma façon de faire au cas où cela pourrait aider quelqu'un.

Ce que je fais, c'est avoir un générique 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) }

Et puis je crée des colis comme ceci:

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

Ce qui me débarrasse de ce remplacement passe-partout.

gmemario
la source
4
  • Utilisez l' annotation @Parcelize en plus de votre classe Model / Data
  • Utilisez la dernière version de Kotlin
  • Utilisez la dernière version des extensions Android Kotlin dans votre module d'application

Exemple :

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

Malheureusement, il n'y a aucun moyen dans Kotlin de mettre un champ réel dans une interface, vous ne pouvez donc pas l'hériter d'un adaptateur d'interface gratuitement: data class Par : MyParcelable

Vous pouvez regarder la délégation, mais cela n'aide pas avec les champs, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Donc, la seule option que je vois est une fonction Fabric Parcelable.Creator, ce qui est assez évident.

voddan
la source
2

je préfère simplement utiliser la https://github.com/johncarl81/parceler lib avec

@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)
Jan Rabe
la source
Cela donne l'erreur "La classe 'MyClass' n'est pas abstraite et n'implémente pas le membre abstrait public abstract fun writeToParcel (dest: Parcel !, flags: Int): Unité définie dans android.os.Parcelable
PhillyTheThrilly
2

Vous pouvez le faire en utilisant l' @Parcelizeannotation. C'est un générateur d'implémentation Parcelable automatique.

Tout d'abord, vous devez les activer en ajoutant ceci à build.gradle de votre module:

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

Déclarez les propriétés sérialisées dans un constructeur principal et ajoutez une @Parcelizeannotation, et les méthodes writeToParcel()/ createFromParcel()seront créées automatiquement:

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

Vous n'avez pas besoin d'ajouter un bloc experimental = trueintérieur androidExtensions.

Malwinder Singh
la source
1

Kotlin a rendu tout le processus de parcellisation sous Android extrêmement facile avec son annotation @Parcel.

Pour faire ça

Étape 1. Ajoutez des extensions Kotlin dans votre module d'application gradle

Étape 2. Ajoutez experimental = true car cette fonctionnalité est toujours en expérimentation dans gradle.

androidExtensions {experimental = true}

Étape 3. Annonez la classe de données avec @Parcel

Voici un exemple simple d'utilisation de @Parcel

Ramakrishna Joshi
la source