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

Posted

技术标签:

【中文标题】有没有一种方便的方法可以使用 Kotlin 在 Android 中创建 Parcelable 数据类?【英文标题】:Is there a convenient way to create Parcelable data classes in Android with Kotlin? 【发布时间】:2016-02-06 17:24:25 【问题描述】:

我目前在我的 Java 项目中使用了出色的 AutoParcel,它有助于 Parcelable 类的创建。

现在,我为下一个项目考虑的 Kotlin 具有数据类的概念,它会自动生成 equals、hashCode 和 toString 方法。

有没有一种方便的方法可以方便地使 Kotlin 数据类 Parcelable(无需手动实现方法)?

【问题讨论】:

你试过 kapt 吗? blog.jetbrains.com/kotlin/2015/06/… 您的意思是在 Kotlin 中使用 AutoParcel?我考虑过这一点,但如果有一种方法可以将数据类 Parcelable 内置到语言中,那就太好了。特别考虑到 Kotlin 的很大一部分使用将来自 android 应用程序。 【参考方案1】:

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
    

【讨论】:

想看的朋友:blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out 为什么这不再是数据类。这个例子是否只是为了表明这可以应用于任何通用的 kotlin 类? 编译器抱怨this calss implements Parcelable but does not provice CREATOR field。你的答案够不够(完整)? @murt 你成功使用 Parcelable 了吗?由于 CREATOR,我面临编译错误 您可以使用@SuppressLint("ParcelCreator") 来消除 lint 警告。【参考方案2】:

你可以试试这个插件:

android-parcelable-intellij-plugin-kotlin

它可以帮助您为 kotlin 的数据类生成 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)
            
        
    

【讨论】:

【参考方案3】:

只需点击你的kotlin数据类的data关键字,然后按alt+Enter,选择第一个选项"Add Parceable Implementation"

【讨论】:

我用了这个方法,但是有几个问题。如果字段不是val/var,有时它会用TODO 替换parcel.read...。如果你在一个类中使用List,你的实现就会成为一个问题。所以我在接受的答案中转向@Parcelize【参考方案4】:

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

用法:

使用@PaperParcel 注释您的数据类,实现PaperParcelable,并添加生成的CREATOR 的JVM 静态实例,例如:

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

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

编辑:用最新的 API 更新

【讨论】:

IDE 现在显示“禁止从其他类继承数据类”。我认为 PaperParcel 不能再用于数据类... 它们永远不能从其他类继承,但它们可以实现接口(例如 PaperParcelable) @Bradley Campbell 这让我在这一行出现错误 JvmField val CREATOR = PaperParcelExample.CREATOR cannot create PaperParcelExample class【参考方案5】:

没有样板代码的最佳方法是Smuggler gradle 插件。您只需要实现 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 插件,它有很多其他功能可能是不必要的。

【讨论】:

【参考方案6】: 在模型/数据类之上使用 @Parcelize 注释 使用最新版本的 Kotlin 在您的应用模块中使用最新版本的 Kotlin Android 扩展

例子:

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

【讨论】:

【参考方案7】:

一个更简单和最新的方法是使用@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

【讨论】:

【参考方案8】:

使用 Android StudioKotlin 插件,我找到了一种简单的方法来转换我的旧 Java Parcelables,没有额外的插件 (如果您只想将全新的 data 类变成 Parcelable,请跳到第 4 个代码 sn-p)。

假设您有一个 Person 类,其中包含所有 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 &gt; Convert Java file to Kotlin File 选项发挥它的魔力:

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

将其转换为data 类:

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

最后,让我们再次把它变成Parcelable。将鼠标悬停在类名上,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 类定义中。

注意事项:

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

我希望这个技巧对你有用,对我也有用。

【讨论】:

【参考方案9】:

您可以使用@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

【讨论】:

【参考方案10】:

我会放弃我的做法,以防它可能对某人有所帮助。

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

然后我创建这样的包裹:

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

这让我摆脱了样板覆盖。

【讨论】:

【参考方案11】:

不幸的是,在 Kotlin 中没有办法在接口中放置一个真实的字段,所以你不能从接口适配器免费继承它: data class Par : MyParcelable

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

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

【讨论】:

【参考方案12】:

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

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

【讨论】:

这给出了错误“类 'MyClass' 不是抽象的并且没有实现抽象成员 public abstract fun writeToParcel(dest: Parcel!, flags: Int): Unit defined in android.os.Parcelable 【参考方案13】:

Kotlin 的 @Parcel 注解让 Android 中的整个 Parcelization 过程变得非常简单。

这样做

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

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

androidExtensions 实验=真

第 3 步。 使用 @Parcel 标注数据类

Here 是一个关于@Parcel 用法的简单示例

【讨论】:

【参考方案14】:

以下 Kotlin 代码可以帮助您使用 Parent 和 Child Data 类创建 Parcelable 数据类

父数据类 - 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)
        
    

子数据类 - 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)
        
    

【讨论】:

【参考方案15】:

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

替代方案: 我有一个使用Parcelable 和列表的自定义数据类的工作示例:

使用 Parcelable 和 Lists 的数据类:

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

希望对你有帮助!

【讨论】:

【参考方案16】:

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

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

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

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

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

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

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

【讨论】:

以上是关于有没有一种方便的方法可以使用 Kotlin 在 Android 中创建 Parcelable 数据类?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Kotlin 中获取 Int 的二进制表示

kotlin 简单介绍以及使用总结

kotlin 简单介绍以及使用总结

Kotlin Hello World

[Kotlin] 内部类

Kotlin基础初探