有没有一种方便的方法可以使用 Kotlin 在 Android 中创建 Parcelable 数据 类?

Is there a convenient way to create Parcelable data classes in Android with Kotlin?

我目前在我的 Java 项目中使用优秀的 AutoParcel,它有助于创建 Parcelable classes。

现在,我考虑将其用于下一个项目的 Kotlin 具有这种数据概念 classes,它会自动生成 equals、hashCode 和 toString 方法。

有没有方便的方法使 Kotlin 数据 class 以方便的方式可打包(无需手动实现方法)?

不幸的是,Kotlin 无法在接口中放置一个真实的字段,因此您不能免费从接口适配器继承它: data class Par : MyParcelable

您可能会查看委派,但它对字段没有帮助,AFAIK:https://kotlinlang.org/docs/reference/delegation.html

所以我看到的唯一选择是 Parcelable.Creator 的结构函数,这很明显。

有一个插件,但并不总是随着 Kotlin 的发展而更新: https://plugins.jetbrains.com/plugin/8086

选择: 我有一个使用 Parcelable 和列表的自定义数据 class 的工作示例:

数据 classes 使用带有列表的 Parcelable:

https://gist.github.com/juanchosaravia/0b61204551d4ec815bbf

希望对您有所帮助!

你可以试试这个插件:

android-parcelable-intellij-plugin-kotlin

它帮助您为 kotlin 数据 class 生成 Android Parcelable 样板代码。最后看起来像这样:

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

你试过PaperParcel了吗?它是一个注释处理器,可以自动为您生成 Android Parcelable 样板代码。

用法:

@PaperParcel 注释您的数据 class,实施 PaperParcelable,并添加生成的 CREATOR 的 JVM 静态实例,例如:

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

现在你的数据class是Parcelable,可以直接传递给BundleIntent

编辑:更新最新API

我会留下我的做法,以防对某人有帮助。

我所做的是我有一个通用的 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) }

然后我像这样创建 parcelables:

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

这让我摆脱了样板覆盖。

我更喜欢将 https://github.com/johncarl81/parceler 库与

一起使用
@Parcel(Parcel.Serialization.BEAN)
data class MyClass(val value)

完全没有样板代码的最佳方式是Smugglergradle插件。您所需要的只是实现 AutoParcelable 接口,例如

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

仅此而已。也适用于密封 类。此插件还为所有 AutoParcelable 类 提供编译时验证。

UPD 17.08.2017 现在 Kotlin 1.1.4 and Kotlin Android extensions plugin 您可以使用 @Parcelize 注释。在这种情况下,上面的示例将如下所示:

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

不需要 data 修饰符。目前最大的缺点是使用 kotlin-android-extensions 插件,它有很多其他可能不需要的功能。

使用 Android StudioKotlin 插件,我找到了一种简单的方法来转换我的旧 Java Parcelables 没有额外的插件(如果你只想把一个全新的 data class 变成 Parcelable,跳到第 4 个代码片段)。

假设您有一个 Person class 和所有 Parcelable 样板:

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

首先剥离 Parcelable 实现,留下一个基本的、普通的、旧的 Java 对象(属性应该是最终的并由构造函数):

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

然后让 Code > Convert Java file to Kotlin File 选项发挥它的魔力:

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

将其转换为 data class:

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

最后,让我们再次将其变成 Parcelable。将鼠标悬停在 class 名称上,Android Studio 应该会为您提供 Add Parcelable Implementation 的选项。结果应如下所示:

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

如您所见,Parcelable 实现是一些自动生成的代码附加到您的 data class 定义。

备注:

  1. 尝试将 Java Parcelable 直接转换为 Kotlin 不会产生与Kotlin 插件的当前版本 (1.1.3).
  2. 我不得不删除当前 Parcelable 代码生成器引入的一些额外的花括号。应该是小bug。

我希望这个技巧对你和我一样有用。

Kotlin 1.1.4出局

Android 扩展插件现在包含一个自动 Parcelable 实现生成器。在主构造函数中声明序列化属性并添加@Parcelize 注释,writeToParcel()/createFromParcel() 方法将自动创建:

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

所以你需要启用他们将这个添加到你模块的 build.gradle:

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

android {
    androidExtensions {
        experimental = true
    }
}

只需单击您的 kotlin 数据的数据关键字 class,然后按 alt+Enter,select 第一个选项显示 "Add Parceable Implementation"

Kotlin 通过其 @Parcel 注释使 Android 中的整个 Parcelization 过程非常简单。

这样做

步骤 1. 在您的应用模块中添加 Kotlin 扩展 gradle

第 2 步。 添加实验性 = true,因为此功能在 gradle 中仍在实验中。

androidExtensions { experimental = true }

步骤 3. 使用 @Parcel

注释数据 class

Here 是@Parcel 用法的简单示例

  • 在模型/数据之上使用 @Parcelize 注释 class
  • 使用最新版本的 Kotlin
  • 在您的应用模块中使用最新版本的 Kotlin Android 扩展

示例:

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

您可以使用 @Parcelize 注释来完成。它是一个自动 Parcelable 实现生成器。

首先,您需要启用它们,将其添加到您模块的 build.gradle:

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

在主构造函数中声明序列化属性并添加@Parcelize注解,writeToParcel()/createFromParcel()方法将自动创建:

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

不要需要在androidExtensions块中添加experimental = true

下面的 Kotlin 代码可以帮助您创建 Parcelable 数据 classes with Parent and Child Data class

父数据Class - MyPostModel

data class MyPostModel(
    @SerializedName("post_id") val post_id: String? = "",
    @SerializedName("category_id") val category_id: String? = "",
    @SerializedName("images") val images: ArrayList<ImagesModel>? = null
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString(),
        parcel.createTypedArrayList(ImagesModel.CREATOR)
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(post_id)
        parcel.writeString(category_id)
        parcel.writeTypedList(images)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<MyPostModel> {
        override fun createFromParcel(parcel: Parcel): MyPostModel {
            return MyPostModel(parcel)
        }

        override fun newArray(size: Int): Array<MyPostModel?> {
            return arrayOfNulls(size)
        }
    }
}

子数据 Class - ImagesModel

data class ImagesModel(
    @SerializedName("image_id") val image_id: String? = "",
    @SerializedName("image_url") val image_url: String? = ""
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(image_id)
        parcel.writeString(image_url)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<ImagesModel> {
        override fun createFromParcel(parcel: Parcel): ImagesModel {
            return ImagesModel(parcel)
        }

        override fun newArray(size: Int): Array<ImagesModel?> {
            return arrayOfNulls(size)
        }
    }
}

一个更简单和最新的方法是使用 @Parcelize 注释,它消除了实现 Parcelable

的艰苦工作

build.gradle(应用模块)

apply plugin: "kotlin-parcelize"

// If you are using the new plugin format use this instead.
plugins{
    ...
    id "kotlin-parcelize"
}

YourClass.kt

import kotlinx.parcelize

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

由于没有人提到这一点(或者更确切地说,没有提供关于版本的具体细节),我将对此进行快速评论:

我按照上述指导尝试了 @Parcelize 的所有可能组合,但未能成功。

如果您的 Android 工作室在 class 上显示 describeContentswriteToParcel 未实现的错误,即使有 @Parcelize 注释,请检查在您的 app/build.gradle 中您没有启用 apply plugin: 'kotlin-android-extensions'

这是行不通的。出于某种原因,我的项目使用旧的已弃用版本的扩展 (ktxVersion = '1.0.2')。 所以这与 @Parcelize 实施相冲突。

当我尝试添加 apply plugin: 'kotlin-parcelize' 时,它说它不能同时使用扩展,我只能使用一个或另一个。

与同事合作后,我们发现扩展导致整个 @Parcelize 失败。我尝试了很多不同的东西,但即使在克服了编译时错误之后,运行时错误也会说“CREATOR”未找到异常或类似的。

所以最后我为 kotlin-android-extensions 删除了 apply plugin 并且只包含 apply plugin: 'kotlin-parcelize',这解决了那个问题,它按预期工作。