Czy istnieje wygodny sposób tworzenia klas danych Parcelable w systemie Android za pomocą Kotlin?


118

Obecnie w moim projekcie Java używam doskonałej AutoParcel , która ułatwia tworzenie klas Parcelable.

Teraz Kotlin, który rozważam w moim następnym projekcie, ma tę koncepcję klas danych, które automatycznie generują metody equals, hashCode i toString.

Czy istnieje wygodny sposób, aby w wygodny sposób utworzyć klasę danych Kotlin Parcelable (bez ręcznego wdrażania metod)?



Masz zamiar korzystać z AutoParcel z Kotlin? Myślałem o tym, ale gdyby istniał sposób na wbudowanie w język klas danych Parcelable, byłoby pięknie. Szczególnie biorąc pod uwagę ogromną część wykorzystania Kotlina, będą pochodziły z aplikacji na Androida.
— thalesmello

Odpowiedzi:


188

Kotlin 1.1.4 jest niedostępny

Wtyczka Android Extensions zawiera teraz automatyczny generator implementacji Parcelable. Zadeklaruj zserializowane właściwości w głównym konstruktorze i dodaj adnotację @Parcelize, a metody writeToParcel () / createFromParcel () zostaną utworzone automatycznie:

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

Musisz więc umożliwić im dodanie tego do pliku build.gradle modułu :

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

android {
    androidExtensions {
        experimental = true
    }
}

2
Dla tych, którzy chcą to sprawdzić: blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out
— thalesmello

3
dlaczego to już nie jest klasa danych. Czy ten przykład ma na celu tylko pokazanie, że można to zastosować do dowolnej ogólnej klasy kotlin?
— Nitin Jain

10
kompilator narzeka this calss implements Parcelable but does not provice CREATOR field. Czy Twoja odpowiedź jest wystarczająca (pełna)?
— murt

1
@murt Czy pomyślnie korzystałeś z Parcelable? Mam do czynienia z błędem kompilacji z powodu CREATOR
— TOP,

4
Możesz użyć, @SuppressLint("ParcelCreator")aby pozbyć się ostrzeżenia o kłaczkach.
— Dutch Masters

47

Możesz wypróbować tę wtyczkę:

android-parcelable-intellij-plugin-kotlin

Pomaga w generowaniu standardowego kodu Android Parcelable dla klasy danych kotlin. I w końcu wygląda to tak:

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

Po prostu kliknij słowo kluczowe data swojej klasy danych kotlin, a następnie naciśnij alt + Enter, wybierz pierwszą opcję mówiącą "Add Parceable Implementation"


2
Użyłem tej metody, ale ma kilka problemów. Czasami zastępuje parcel.read...z TODO, jeśli pole nie jest val/var. Jeśli używasz Listwewnątrz klasy, twoja implementacja staje się problemem. Więc zwróciłem się do @Parcelizezaakceptowanej odpowiedzi.
— CoolMind

19

Czy próbowałeś PaperParcel ? Jest to procesor adnotacji, który automatycznie generuje Parcelabledla Ciebie standardowy kod Androida .

Stosowanie:

Dodaj adnotacje do swojej klasy danych @PaperParcel, zaimplementuj PaperParcelablei dodaj statyczną instancję JVM wygenerowanego CREATORnp .:

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

Teraz twoja klasa danych jest Parcelablei może być przekazywana bezpośrednio do BundlelubIntent

Edycja: aktualizacja z najnowszym interfejsem API


Środowisko IDE mówi teraz: „Dziedziczenie klas danych z innych klas jest zabronione”. Myślę, że PaperParcel nie może być już używany z klasami danych ...
— Massimo

Nigdy nie mogli dziedziczyć z innych klas, ale potrafią implementować interfejsy (np. PaperParcelable)
— Bradley Campbell

1
@Bradley Campbell To daje mi błąd w tej linii JvmField val CREATOR = PaperParcelExample.CREATOR nie może utworzyć klasy
— PaperParcelExample

16

Najlepszy sposób z żadnym boilerplate kodu w ogóle jest Smuggler wtyczki Gradle. Wystarczy zaimplementować interfejs AutoParcelable, taki jak

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

I to wszystko. Działa również dla klas zamkniętych. Również ta wtyczka zapewnia walidację czasu kompilacji dla wszystkich klas AutoParcelable.

UPD 17.08.2017 Teraz z wtyczką Kotlin 1.1.4 i Kotlin Android można używać @Parcelizeadnotacji. W takim przypadku powyższy przykład będzie wyglądał następująco:

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

Nie ma potrzeby datamodyfikatora. Największą wadą na razie jest użycie wtyczki kotlin-android-extensions, która ma wiele innych funkcji, które mogą być niepotrzebne.


6

Korzystanie z Androidem Studio i Kotlin wtyczki znalazłem łatwy sposób przekonwertować mój stary Java Parcelablesz bez dodatkowych wtyczek (jeśli chcesz to, aby włączyć nową dataklasę na Parcelable, przejdź do 4 fragmencie kodu).

Powiedzmy, że masz Personklasę z całą Parcelablepłytą kotłową:

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

Zacznij od usunięcia Parcelableimplementacji, pozostawiając nieskomplikowany, zwykły, stary obiekt Java (właściwości powinny być ostateczne i ustawione przez konstruktora):

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

Następnie niech Code > Convert Java file to Kotlin Fileopcja zrobi swoje:

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

Przekształć to w dataklasę:

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

Na koniec zmieńmy to Parcelableponownie w plik . Najedź na nazwę klasy, a Android Studio powinno dać Ci opcję Add Parcelable Implementation. Wynik powinien wyglądać następująco:

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

Jak widać, Parcelableimplementacja to jakiś automatycznie generowany kod dołączany do datadefinicji klasy.

Uwagi:

  1. Próba konwersji Javy Parcelable bezpośrednio do Kotlin nie przyniesie takich samych rezultatów z aktualną wersją wtyczki Kotlin ( 1.1.3).
  2. Musiałem usunąć kilka dodatkowych nawiasów klamrowych, które wprowadza obecny Parcelablegenerator kodu. To musi być drobny błąd.

Mam nadzieję, że ta wskazówka zadziała tak dobrze dla Ciebie, jak i dla mnie.


5
  • Użyj adnotacji @Parcelize na górze klasy Model / Data
  • Użyj najnowszej wersji Kotlin
  • Użyj najnowszej wersji Kotlin Android Extensions w swoim module aplikacji

Przykład:

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

4

Zostawię swój sposób postępowania na wypadek, gdyby mogło to komuś pomóc.

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

A potem tworzę paczki w ten sposób:

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

Co pozwala mi pozbyć się tego standardowego zastąpienia.


3

Niestety w Kotlinie nie ma możliwości umieszczenia prawdziwego pola w interfejsie, więc nie można go odziedziczyć z adaptera interfejsu za darmo: data class Par : MyParcelable

Możesz spojrzeć na delegację, ale to nie pomoże z polami, AFAIK: https://kotlinlang.org/docs/reference/delegation.html

Więc jedyną opcją, jaką widzę, jest funkcja tkaniny Parcelable.Creator, co jest dość oczywiste.


2

wolę po prostu korzystać z biblioteki https://github.com/johncarl81/parceler z

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

Powoduje to błąd „Klasa 'MyClass' nie jest abstrakcyjna i nie implementuje elementu abstrakcyjnego public abstract fun writeToParcel (dest: Parcel !, flags: Int): Jednostka zdefiniowana w android.os.Parcelable
— PhillyTheThrilly

2

Możesz to zrobić za pomocą @Parcelizeadnotacji. Jest to automatyczny generator realizacji Parcelable.

Najpierw musisz umożliwić im dodanie tego do pliku build.gradle modułu:

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

Zadeklaruj serializowane właściwości w głównym konstruktorze i dodaj @Parcelizeadnotację, a writeToParcel()/ createFromParcel()metody zostaną utworzone automatycznie:

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

Ty dont trzeba dodać experimental = truewewnątrz androidExtensionsbloku.


1

Kotlin bardzo ułatwił cały proces Parcelizacji w Androidzie dzięki adnotacji @Parcel.

Aby to zrobić

Krok 1. Dodaj rozszerzenia Kotlin w gradle modułu aplikacji

Krok 2. Dodaj eksperymentalne = true, ponieważ ta funkcja jest nadal w fazie eksperymentów w gradle.

androidExtensions {experimental = true}

Krok 3. Ogłoś klasę danych za pomocą @Parcel

Oto prosty przykład użycia @Parcel


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.