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

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

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

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

【问题讨论】:

  • 您的意思是在 Kotlin 中使用 AutoParcel?我考虑过这一点,但如果有一种方法可以将数据类 Parcelable 内置到语言中,那就太好了。特别考虑到 Kotlin 的很大一部分使用将来自 Android 应用程序。

标签: android parcelable kotlin


【解决方案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
    }
}

【讨论】:

  • 为什么这不再是数据类。这个例子是否只是为了表明这可以应用于任何通用的 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 类定义中。

          注意事项:

          1. 尝试将 Java Parcelable 直接转换为 Kotlin 不会产生与当前版本的 Kotlin 插件相同的结果( 1.1.3)。
          2. 我不得不删除当前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',它解决了这个问题,它按预期工作。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-11-22
                          • 1970-01-01
                          • 2010-09-06
                          • 2014-01-02
                          • 1970-01-01
                          • 2014-03-25
                          • 1970-01-01
                          相关资源
                          最近更新 更多