Esiste un modo conveniente per creare classi di dati parcellizzabili in Android con Kotlin?


117

Attualmente sto utilizzando l'eccellente AutoParcel nel mio progetto Java, che facilita la creazione di classi Parcelable.

Ora, Kotlin, che considero per il mio prossimo progetto, ha questo concetto di classi di dati, che generano automaticamente i metodi uguale, hashCode e toString.

Esiste un modo conveniente per rendere una classe di dati Kotlin parcellabile in modo conveniente (senza implementare i metodi manualmente)?



Intendi usare AutoParcel con Kotlin? Ci ho pensato, ma se ci fosse un modo per avere classi di dati Parcelable incorporate nel linguaggio, sarebbe bello. Soprattutto considerando che una parte enorme dell'utilizzo di Kotlin proverrà dalle applicazioni Android.
Thalesmello

Risposte:


188

Kotlin 1.1.4 è uscito

Il plugin Android Extensions ora include un generatore di implementazione Parcelable automatico. Dichiarare le proprietà serializzate in un costruttore principale e aggiungere un'annotazione @Parcelize, ei metodi writeToParcel () / createFromParcel () verranno creati automaticamente:

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

Quindi devi abilitarli aggiungendo questo al build.gradle del tuo modulo :

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

android {
    androidExtensions {
        experimental = true
    }
}


3
perché questa non è più una classe di dati. Questo esempio serve solo a dimostrare che questo potrebbe essere applicato a qualsiasi classe kotlin generica?
Nitin Jain

10
il compilatore si lamenta this calss implements Parcelable but does not provice CREATOR field. La tua risposta è sufficiente (completa)?
murt

1
@murt Hai usato con successo Parcelable? Sto affrontando l'errore di compilazione a causa di CREATOR
TOP

4
È possibile utilizzare @SuppressLint("ParcelCreator")per eliminare l'avviso di lanugine.
Dutch Masters

47

Puoi provare questo plugin:

Android-Parcelable-IntelliJ-plugin-Kotlin

Ti aiuta a generare codice boilerplate Android Parcelable per la classe di dati di kotlin. E finalmente assomiglia a questo:

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

20

Basta fare clic sulla parola chiave data della tua classe di dati kotlin, quindi premere alt + Invio, selezionare la prima opzione che dice "Add Parceable Implementation"


2
Ho usato questo metodo, ma ha diversi problemi. A volte sostituisce a parcel.read...con TODO, se un campo non lo è val/var. Se usi Listall'interno di una classe, la tua implementazione diventa un problema. Quindi mi sono rivolto alla @Parcelizerisposta accettata.
CoolMind

19

Hai provato PaperParcel ? È un processore di annotazioni che genera automaticamente il Parcelablecodice boilerplate di Android per te.

Uso:

Annota la tua classe di dati con @PaperParcel, implementa PaperParcelablee aggiungi un'istanza statica JVM dell'oggetto generato, CREATORad esempio:

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

Ora la tua classe di dati è Parcelablee può essere passata direttamente a BundleoIntent

Modifica: aggiorna con l'ultima API


L'IDE ora dice "L'ereditarietà della classe di dati da altre classi è vietata". Penso che PaperParcel non possa più essere utilizzato con classi di dati ...
Massimo

Non potrebbero mai ereditare da altre classi, ma possono implementare interfacce (ad esempio PaperParcelable)
Bradley Campbell

1
@Bradley Campbell Questo mi dà un errore in questa riga JvmField val CREATOR = PaperParcelExample.CREATOR non può creare la classe
PaperParcelExample

16

Il modo migliore senza alcun codice boilerplate è il plug-in Gradle di Smuggler . Tutto ciò di cui hai bisogno è implementare l'interfaccia AutoParcelable come

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

E questo è tutto. Funziona anche per classi sigillate. Anche questo plugin fornisce la convalida in fase di compilazione per tutte le classi AutoParcelable.

UPD 17.08.2017 Ora con Kotlin 1.1.4 e il plug-in delle estensioni Android Kotlin è possibile utilizzare l' @Parcelizeannotazione. In questo caso, l'esempio sopra sarà simile a:

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

Non c'è bisogno di datamodificatore. Il più grande svantaggio, per ora, è l'utilizzo del plugin kotlin-android-extensions che ha molte altre funzioni che potrebbero non essere necessarie.


6

Utilizzando Android Studio e la Kotlin plug-in, ho trovato un modo semplice per convertire il mio vecchio Java Parcelables con nessun plug-in aggiuntivi (se tutto quello che volete è quello di trasformare un nuovo marchio datadi classe in una Parcelable, passare al frammento di codice 4 °).

Supponiamo che tu abbia una Personclasse con tutta la Parcelablepiastra della caldaia:

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

Inizia eliminando l' Parcelableimplementazione, lasciando un oggetto Java semplice, semplice e vecchio (le proprietà dovrebbero essere finali e impostate dal costruttore):

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

Quindi lascia che l' Code > Convert Java file to Kotlin Fileopzione faccia la sua magia:

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

Converti questo in una dataclasse:

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

E infine, trasformiamo di nuovo questo in un file Parcelable. Passa il mouse sul nome della classe e Android Studio dovrebbe darti la possibilità di farlo Add Parcelable Implementation. Il risultato dovrebbe essere simile a questo:

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

Come puoi vedere, l' Parcelableimplementazione è un codice generato automaticamente aggiunto alla datadefinizione della tua classe.

Appunti:

  1. Il tentativo di convertire un Java Parcelable direttamente in Kotlin non produrrà lo stesso risultato con la versione corrente del plugin Kotlin ( 1.1.3).
  2. Ho dovuto rimuovere alcune parentesi graffe extra Parcelableintrodotte dal generatore di codice corrente . Deve essere un bug minore.

Spero che questo suggerimento funzioni per te come per me.


4

Lascerò il mio modo di fare nel caso possa aiutare qualcuno.

Quello che faccio è avere un generico 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 poi creo pacchi come questo:

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

Il che mi fa sbarazzare di quell'override boilerplate.


4
  • Usa l' annotazione @Parcelize sopra la tua classe Model / Data
  • Usa l'ultima versione di Kotlin
  • Usa l'ultima versione delle estensioni Android di Kotlin nel modulo dell'app

Esempio :

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

3

Sfortunatamente non c'è modo in Kotlin di mettere un campo reale in un'interfaccia, quindi non puoi ereditarlo gratuitamente da un adattatore di interfaccia: data class Par : MyParcelable

Puoi guardare la delega, ma non aiuta con i campi, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Quindi l'unica opzione che vedo è una funzione fabric Parcelable.Creator, il che è abbastanza ovvio.


2

preferisco semplicemente usare la libreria https://github.com/johncarl81/parceler con

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

Questo restituisce l'errore "La classe 'MyClass' non è astratta e non implementa abstract member public abstract fun writeToParcel (dest: Parcel !, flags: Int): Unità definita in android.os.Parcelable
PhillyTheThrilly

2

Puoi farlo usando l' @Parcelizeannotazione. È un generatore di implementazione Parcelable automatico.

Per prima cosa, devi abilitarli aggiungendo questo al build.gradle del tuo modulo:

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

Dichiara le proprietà serializzate in un costruttore principale e aggiungi @Parcelizeun'annotazione, e writeToParcel()/ createFromParcel()metodi verranno creati automaticamente:

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

È NON necessità di aggiungere experimental = trueall'interno androidExtensionsdel blocco.


1

Kotlin ha reso l'intero processo di parcellizzazione in Android dannatamente facile con la sua annotazione @Parcel.

Fare quello

Passaggio 1. Aggiungi le estensioni Kotlin nel gradle del modulo dell'app

Passaggio 2. Aggiungi sperimentale = vero poiché questa funzione è ancora in fase di sperimentazione in gradle.

androidExtensions {sperimentale = true}

Passaggio 3. Annona la classe di dati con @Parcel

Ecco un semplice esempio sull'utilizzo di @Parcel


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.