Gibt es eine bequeme Möglichkeit, Parcelable-Datenklassen in Android mit Kotlin zu erstellen?


116

Ich verwende derzeit das hervorragende AutoParcel in meinem Java-Projekt, das die Erstellung von Parcelable-Klassen erleichtert.

Jetzt hat Kotlin, das ich für mein nächstes Projekt in Betracht ziehe, dieses Konzept von Datenklassen, die automatisch die Methoden equals, hashCode und toString generieren.

Gibt es eine bequeme Möglichkeit, eine Kotlin-Datenklasse auf bequeme Weise parcelbar zu machen (ohne die Methoden manuell zu implementieren)?


2
Hast du es mit kapt versucht? blog.jetbrains.com/kotlin/2015/06/…
Sergey Mashkov

Sie möchten AutoParcel mit Kotlin verwenden? Ich habe darüber nachgedacht, aber wenn es eine Möglichkeit gäbe, Datenklassen Parcelable in die Sprache zu integrieren, wäre es schön. Insbesondere wenn man bedenkt, dass ein großer Teil der Kotlin-Nutzung von Android-Anwendungen stammt.
Thalesmello

Antworten:


187

Kotlin 1.1.4 ist raus

Das Android Extensions-Plugin enthält jetzt einen automatischen Parcelable-Implementierungsgenerator. Deklarieren Sie die serialisierten Eigenschaften in einem primären Konstruktor und fügen Sie eine @ Parcelize-Annotation hinzu. Die Methoden writeToParcel () / createFromParcel () werden automatisch erstellt:

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

Sie müssen sie also aktivieren, um dies dem build.gradle Ihres Moduls hinzuzufügen :

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

android {
    androidExtensions {
        experimental = true
    }
}

2
Für diejenigen, die es ausprobieren
möchten

3
Warum ist das nicht mehr Datenklasse. Soll dieses Beispiel nur zeigen, dass dies auf jede generische Kotlin-Klasse angewendet werden kann?
Nitin Jain

10
Compiler beschwert sich this calss implements Parcelable but does not provice CREATOR field. Ist Ihre Antwort ausreichend (vollständig)?
verletzt

1
@murt Haben Sie Parcelable erfolgreich eingesetzt? Ich bin mit dem Kompilierungsfehler wegen CREATOR
TOP

4
Sie können verwenden @SuppressLint("ParcelCreator"), um die Flusenwarnung loszuwerden.
Dutch Masters

47

Sie können dieses Plugin ausprobieren:

Android-Paket-Intellij-Plugin-Kotlin

Es hilft Ihnen beim Generieren von Android Parcelable Boilerplate-Code für die Datenklasse von kotlin. Und es sieht endlich so aus:

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

19

Haben Sie PaperParcel ausprobiert ? Es ist ein Anmerkungsprozessor, der automatisch den Android- ParcelableBoilerplate-Code für Sie generiert .

Verwendung:

Anmerken Ihre Datenklasse mit @PaperParcel, implementieren PaperParcelable, und fügen Sie eine JVM statische Instanz des erzeugten CREATORzB:

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

Jetzt ist Parcelableund kann Ihre Datenklasse direkt an ein Bundleoder übergeben werdenIntent

Bearbeiten: Update mit der neuesten API


Die IDE sagt jetzt "Datenklassenvererbung von anderen Klassen ist verboten". Ich denke, PaperParcel kann nicht mehr mit Datenklassen verwendet werden ...
Massimo

Sie könnten niemals von anderen Klassen erben, aber sie können Schnittstellen implementieren (z. B. PaperParcelable)
Bradley Campbell

1
@Bradley Campbell Dies gibt mir einen Fehler in dieser Zeile JvmField val CREATOR = PaperParcelExample.CREATOR kann keine PaperParcelExample-Klasse erstellen
Mr.G

18

Klicken Sie einfach auf das Datenschlüsselwort Ihrer Kotlin-Datenklasse, drücken Sie Alt + Eingabetaste und wählen Sie die erste Option aus "Add Parceable Implementation"


2
Ich habe diese Methode verwendet, aber sie hat mehrere Probleme. Manchmal ersetzt es ein parcel.read...mit TODO, wenn ein Feld nicht ist val/var. Wenn Sie Listinnerhalb einer Klasse verwenden, wird Ihre Implementierung zu einem Problem. Also wandte ich mich an @Parcelizedie akzeptierte Antwort.
CoolMind

16

Der beste Weg ohne Boilerplate- Code ist das Smuggler Gradle Plugin. Alles was Sie brauchen, ist nur die AutoParcelable-Schnittstelle wie zu implementieren

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

Und das ist alles. Funktioniert auch für versiegelte Klassen. Dieses Plugin bietet auch eine Überprüfung der Kompilierungszeit für alle AutoParcelable-Klassen.

UPD 17.08.2017 Mit Kotlin 1.1.4 und dem Kotlin Android Extensions Plugin können Sie jetzt @ParcelizeAnmerkungen verwenden. In diesem Fall sieht das obige Beispiel folgendermaßen aus:

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

Kein dataModifikator erforderlich . Der größte Nachteil ist derzeit die Verwendung des Kotlin-Android-Erweiterungs-Plugins, das viele andere Funktionen bietet, die möglicherweise unnötig sind.


6

Mit Android Studio und dem Kotlin- Plugin habe ich eine einfache Möglichkeit gefunden, meine alten Java- ParcelableDateien ohne zusätzliche Plugins zu konvertieren (wenn Sie nur eine brandneue dataKlasse in eine verwandeln möchtenParcelable , fahren Sie mit dem 4. Code-Snippet fort).

Angenommen, Sie haben eine PersonKlasse mit der gesamten ParcelableKesselplatte:

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

Entfernen Sie zunächst die ParcelableImplementierung und lassen Sie ein einfaches, einfaches, altes Java- Objekt übrig (die Eigenschaften sollten endgültig sein und vom Konstruktor festgelegt werden):

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

Dann lassen Sie die Code > Convert Java file to Kotlin FileOption ihre Magie tun:

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

Wandle dies in eine dataKlasse um:

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

Und zum Schluss machen wir daraus noch Parcelableeinmal eine. Bewegen Sie den Mauszeiger über den Klassennamen und Android Studio sollte Ihnen die Option dazu geben Add Parcelable Implementation. Das Ergebnis sollte folgendermaßen aussehen:

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

Wie Sie sehen können, handelt es sich bei der ParcelableImplementierung um einen automatisch generierten Code, der an Ihre dataKlassendefinition angehängt wird .

Anmerkungen:

  1. Der Versuch, ein Java Parcelable direkt in Kotlin zu konvertieren, führt nicht zum gleichen Ergebnis wie die aktuelle Version des Kotlin- Plugins ( 1.1.3).
  2. Ich musste einige zusätzliche geschweifte Klammern entfernen, die der aktuelle ParcelableCodegenerator einführt. Muss ein kleiner Fehler sein.

Ich hoffe, dieser Tipp funktioniert für Sie genauso gut wie für mich.


4

Ich werde meine Vorgehensweise verlassen, falls es jemandem helfen könnte.

Was ich tue, ist, dass ich ein Generikum habe 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) }

Und dann erstelle ich Pakete wie folgt:

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

Das bringt mich dazu, diesen Boilerplate-Override loszuwerden.


4
  • Verwenden Sie die Annotation @Parcelize über Ihrer Modell- / Datenklasse
  • Verwenden Sie die neueste Version von Kotlin
  • Verwenden Sie die neueste Version von Kotlin Android Extensions in Ihrem App-Modul

Beispiel:

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

3

Leider gibt es in Kotlin keine Möglichkeit, ein reales Feld in eine Schnittstelle einzufügen, sodass Sie es nicht kostenlos von einem Schnittstellenadapter erben können: data class Par : MyParcelable

Sie können sich die Delegierung ansehen, aber es hilft nicht bei Feldern, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Die einzige Option, die ich sehe, ist eine Stofffunktion, für Parcelable.Creatordie es offensichtlich ist.


2

Ich bevorzuge nur die https://github.com/johncarl81/parceler lib mit

@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)

Dies gibt den Fehler "Klasse 'MyClass' ist nicht abstrakt und implementiert kein abstraktes Mitglied public abstract fun writeToParcel (dest: Parcel!, Flags: Int): Einheit definiert in android.os.Parcelable
PhillyTheThrilly

2

Sie können dies mithilfe von @ParcelizeAnmerkungen tun . Es ist ein automatischer Parcelable-Implementierungsgenerator.

Zuerst müssen Sie sie aktivieren, um dies dem build.gradle Ihres Moduls hinzuzufügen:

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

Deklarieren Sie die serialisierten Eigenschaften in einem primären Konstruktor und fügen Sie eine @ParcelizeAnmerkung hinzu. writeToParcel()/ createFromParcel()Methoden werden automatisch erstellt:

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

Sie müssen NICHTexperimental = true innerhalb des androidExtensionsBlocks hinzufügen .


1

Kotlin hat den gesamten Prozess der Paketierung in Android mit seiner Annotation @Parcel verdammt einfach gemacht.

Das zu tun

Schritt 1. Fügen Sie Kotlin-Erweiterungen zu Ihrem App-Modul hinzu

Schritt 2. Fügen Sie experimentell = wahr hinzu, da diese Funktion noch in Gradle experimentiert.

androidExtensions {experimentell = wahr}

Schritt 3. Annonieren Sie die Datenklasse mit @Parcel

Hier ist ein einfaches Beispiel für die Verwendung von @Parcel


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.