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.