¿Existe una forma conveniente de crear clases de datos parcelables en Android con Kotlin?

117

Actualmente estoy usando el excelente AutoParcel en mi proyecto Java, lo que facilita la creación de clases Parcelable.

Ahora, Kotlin, que considero para mi próximo proyecto, tiene este concepto de clases de datos, que generan automáticamente los métodos equals, hashCode y toString.

¿Existe una forma conveniente de hacer que una clase de datos de Kotlin sea Parcelable de una manera conveniente (sin implementar los métodos manualmente)?

thalesmello
fuente
2
¿Intentaste kapt? blog.jetbrains.com/kotlin/2015/06/…
Sergey Mashkov
¿Quiere utilizar AutoParcel con Kotlin? Pensé en eso, pero si hubiera una manera de tener clases de datos Parcelable integradas en el lenguaje, sería hermoso. Especialmente considerando que una gran parte del uso de Kotlin provendrá de aplicaciones de Android.
Thalesmello

Respuestas:

188

Kotlin 1.1.4 está fuera

El complemento de extensiones de Android ahora incluye un generador de implementación Parcelable automático. Declare las propiedades serializadas en un constructor primario y agregue una anotación @Parcelize, y los métodos writeToParcel () / createFromParcel () se crearán automáticamente:

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

Por lo tanto, debe habilitarlos agregando esto a su módulo build.gradle :

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

android {
    androidExtensions {
        experimental = true
    }
}
Dhaval Jivani
fuente
2
Para aquellos que quieran verlo: blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out
thalesmello
3
por qué esta ya no es una clase de datos. ¿Este ejemplo es solo para mostrar que esto podría aplicarse en cualquier clase de kotlin genérica?
Nitin Jain
10
el compilador se queja this calss implements Parcelable but does not provice CREATOR field. ¿Es tu respuesta suficiente (completa)?
murt
1
@murt ¿Usaste Parcelable con éxito? Me enfrento al error de compilación debido a CREATOR
TOP
4
Puede utilizar @SuppressLint("ParcelCreator")para deshacerse de la advertencia de pelusa.
Dutch Masters
47

Puedes probar este complemento:

android-parcelable-intellij-plugin-kotlin

Le ayuda a generar código repetitivo de Android Parcelable para la clase de datos de kotlin. Y finalmente se ve así:

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
fuente
20

Simplemente haga clic en la palabra clave de datos de su clase de datos kotlin, luego presione alt + Enter, seleccione la primera opción que dice "Add Parceable Implementation"

Manish Patiyal
fuente
2
Usé este método, pero tiene varios problemas. A veces sustituye a parcel.read...por TODO, si un campo no lo es val/var. Si lo usa Listdentro de una clase, su implementación se convierte en un problema. Así que recurrí a @Parcelizela respuesta aceptada.
CoolMind
19

¿Ha probado PaperParcel ? Es un procesador de anotaciones que genera automáticamente el Parcelablecódigo repetitivo de Android .

Uso:

Anote su clase de datos con @PaperParcel, implemente PaperParcelabley agregue una instancia estática de JVM del generado, CREATORpor ejemplo:

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

Ahora su clase de datos es Parcelabley se puede pasar directamente a un BundleoIntent

Editar: actualizar con la última API

Bradley Campbell
fuente
El IDE ahora dice que "la herencia de clases de datos de otras clases está prohibida". Creo que PaperParcel ya no se puede usar con clases de datos ...
Massimo
Nunca pudieron heredar de otras clases, pero pueden implementar interfaces (por ejemplo, PaperParcelable)
Bradley Campbell
1
@Bradley Campbell Esto me da un error en esta línea JvmField val CREATOR = PaperParcelExample.CREATOR no puede crear la clase
PaperParcelExample
16

La mejor manera sin ningún código repetitivo es el complemento Smuggler gradle. Todo lo que necesita es implementar la interfaz AutoParcelable como

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

Y eso es todo. También funciona para clases selladas. Además, este complemento proporciona validación en tiempo de compilación para todas las clases AutoParcelable.

UPD 17.08.2017 Ahora, con Kotlin 1.1.4 y el complemento de extensiones de Android Kotlin , puede usar la @Parcelizeanotación. En este caso, el ejemplo anterior se verá así:

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

No es necesario ningún datamodificador. La mayor desventaja, por ahora, es usar el complemento kotlin-android-extensions que tiene muchas otras funciones que podrían ser innecesarias.

Stepango
fuente
6

Usando Android Studio y el complemento Kotlin , encontré una manera fácil de convertir mi antiguo Java Parcelables sin complementos adicionales (si todo lo que desea es convertir una nueva dataclase en unParcelable , pase a la cuarta fragmento de código).

Digamos que tienes una Personclase con toda la Parcelableplaca de la caldera:

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

Comience eliminando la Parcelableimplementación, dejando un objeto Java antiguo, simple y simple (las propiedades deben ser definitivas y las debe establecer el constructor):

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

Entonces deja que la Code > Convert Java file to Kotlin Fileopción haga su magia:

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

Convierta esto en una dataclase:

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

Y finalmente, convierta esto en un Parcelablenuevo. Coloca el cursor sobre el nombre de la clase y Android Studio debería darte la opción Add Parcelable Implementation. El resultado debería verse así:

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 puede ver, la Parcelableimplementación es un código generado automáticamente que se adjunta a datala definición de su clase.

Notas:

  1. Intentar convertir un Java Parcelable directamente en Kotlin no producirá el mismo resultado con la versión actual del complemento Kotlin ( 1.1.3).
  2. Tuve que eliminar algunas llaves adicionales que Parcelableintroduce el generador de código actual . Debe ser un error menor.

Espero que este consejo te funcione tan bien como a mí.

Argenkiwi
fuente
4

Dejaré mi forma de hacer por si pudiera ayudar a alguien.

Lo que hago es tener un 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) }

Y luego creo parcelables 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()) } }
}

Lo que me deshace de esa anulación estándar.

gmemario
fuente
4
  • Utilice @Parcelize anotación en la parte superior de su clase de modelo / datos
  • Utilice la última versión de Kotlin
  • Utilice la última versión de las extensiones de Android Kotlin en el módulo de su aplicación

Ejemplo:

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

Desafortunadamente, no hay forma en Kotlin de poner un campo real en una interfaz, por lo que no puede heredarlo de un adaptador de interfaz de forma gratuita: data class Par : MyParcelable

Puede mirar la delegación, pero no ayuda con los campos, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Entonces, la única opción que veo es una función de tejido Parcelable.Creator, lo cual es algo obvio.

voddan
fuente
2

prefiero usar la https://github.com/johncarl81/parceler lib con

@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)
Jan Rabe
fuente
Esto da el error "La clase 'MyClass' no es abstracta y no implementa el miembro abstracto public abstract fun writeToParcel (dest: Parcel !, flags: Int): Unidad definida en android.os.Parcelable
PhillyTheThrilly
2

Puedes hacerlo usando @Parcelizeanotación. Es un generador automático de implementación Parcelable.

Primero, debe habilitarlos agregando esto al build.gradle de su módulo:

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

Declare las propiedades serializadas en un constructor primario y agregue una @Parcelizeanotación, y los métodos writeToParcel()/ createFromParcel()se crearán automáticamente:

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

Usted NO es necesario añadir experimental = trueen el interior androidExtensionsdel bloque.

Malwinder Singh
fuente
1

Kotlin ha hecho que todo el proceso de paquetería en Android sea muy fácil con su anotación @Parcel.

Para hacer eso

Paso 1. Agregue extensiones de Kotlin en su módulo de aplicación gradle

Paso 2. Agregue experimental = true ya que esta función aún se encuentra en experimentación en gradle.

androidExtensions {experimental = true}

Paso 3. Anone la clase de datos con @Parcel

Aquí hay un ejemplo simple sobre el uso de @Parcel

Ramakrishna Joshi
fuente