NPE在Kotlin数据类中使用@Parcelize批注

时间:2018-07-29 16:05:42

标签: android kotlin kotlin-android-extensions kotlin-extension

我最近将Java模型类迁移到Kotlin Data类。我使用@Parcelize注释来避免Parcelable的样板代码。我在Kotlin中的数据类如下所示,

@Parcelize
data class TimeLine(
    @SerializedName("submittedOnDate")
    var submittedOnDate: List<Int> = ArrayList(),

    @SerializedName("submittedByUsername")
    var submittedByUsername: String,

    @SerializedName("submittedByFirstname")
    var submittedByFirstname: String,

    @SerializedName("submittedByLastname")
    var submittedByLastname: String,

    @SerializedName("approvedOnDate")
    var approvedOnDate: List<Int> = ArrayList(),

    @SerializedName("approvedByUsername")
    var approvedByUsername: String,

    @SerializedName("approvedByFirstname")
    var approvedByFirstname: String,

    @SerializedName("approvedByLastname")
    var approvedByLastname: String,

    @SerializedName("activatedOnDate")
    var activatedOnDate: List<Int>,

    @SerializedName("activatedByUsername")
    var activatedByUsername: String,

    @SerializedName("activatedByFirstname")
    var activatedByFirstname: String,

    @SerializedName("activatedByLastname")
    var activatedByLastname: String,

    @SerializedName("closedOnDate")
    var closedOnDate: List<Int>) : Parcelable`

但是它给了我空指针错误,如下所示,

java.lang.NullPointerException: Attempt to invoke interface method 'int 
java.util.Collection.size()' on a null object reference at 
org.demo.mobile.models.accounts.savings.TimeLine.writeToParcel(TimeLine.kt:0) 
at  org.demo.mobile.models.accounts.savings.SavingAccount.writeToParcel(SavingAccount.kt:0)

我不知道为什么它显示NullPointerException,因为Java Model运行正常。我该如何解决?NPE的背后原因是什么?

3 个答案:

答案 0 :(得分:2)

GSON使用不安全的API来构造您的对象-能够在您声明非空类型的地方放置null。生成的Parcelable实现期望非空值。当您尝试将对象放到包裹中时,它会崩溃。

我看到两个选择:

1)获取实例后手动调整值

您将所有属性声明为var。您可以创建一个扩展方法,为您修复非空值。

@Suppress("SENSELESS_COMPARISON")
fun TimeLine.fixNulls() {
    if (submittedOnDate == null) submittedOnDate = emptyList()
    if (approvedOnDate == null) submittedOnDate = emptyList()
    if (activatedOnDate == null) submittedOnDate = emptyList()
    if (closedOnDate == null) submittedOnDate = emptyList()
}

示例:

val list = gson.fromJson(...).forEach { it.fixNulls() }

或者如果您具有具有val属性的不可变类型:

fun TimeLine.withFixedNulls() = copy(
    submittedOnDate = submittedOnDate ?: emptyList(),
    approvedOnDate = approvedOnDate ?: emptyList(),
    activatedOnDate = activatedOnDate ?: emptyList(),
    closedOnDate = closedOnDate ?: emptyList()
)

示例:

val list = gson.fromJson(...).map { it.withFixedNulls() }

缺点是您必须考虑这一点,并记住在从GSON获得的每个对象上调用它。

这两个变体都有其他问题(内存消耗,并发性)。

2)使用考虑到Kotlin构建的序列化库

您可以使用Moshi Kotlin Codegen。 Moshi与GSON一样,是一个序列化库,但其Kotlin Codegen注释处理器将为您的类型生成特殊的JSON适配器,如果您尝试将null反序列化为不可为null的属性,则会抛出异常。

当您尝试反序列化无效JSON时,它将引发异常,而不是当您尝试使用Kotlin / Java对象时。

免责声明:我只使用了基于相同原理的Moshi + Kotshi

我将把练习作为练习留给您。

令人回味的食物

您不应将大型对象或大量对象放入宗地/意图中。有大小限制。也许您应该使用数据库。

答案 1 :(得分:1)

@Parcelize            
data class TimeLine(

                @field:SerializedName("via")
                val via: MutableList<Int>? = null,

               @field:SerializedName("submittedByUsername")
               val submittedByUsername: String?= null
    ......... so on... 
    ):Parcelable

您的数据模型类与此类似,您需要添加吗? (空安全性)运算符,以便它将接受空值。 有关Null安全性的更多详细信息,请访问Kotlin official documentation

答案 2 :(得分:0)

我遇到了这个问题,发现我的数据类中的一个不可为空的属性在json上收到了空值。

我的数据类:

@Parcelize
data class Person(
    @SerializedName("first_name") val firstName: String,
    @SerializedName("middle_name") val middleName: String,
    @SerializedName("last_name") val lastName: String,
    @SerializedName("birth_date") val birth: String,
    @SerializedName("education") val education: List<Education> // <-- non-nullable property
) : Parcelable {

    @Parcelize
    data class Education(
        @SerializedName("course") val course: String?,
        @SerializedName("school") val school: String?,
    ) : Parcelable

}

JSON:

[
  {
    "first_name": "Steve",
    "middle_name": "James",
    "last_name": "Roberson",
    "birth_date": "March 28, 1960",
    "education": [
      {
        "course": "Course 1",
        "school": "School 1",

      },
      {
        "course": "Course 2",
        "school": "School 2",

      }
    ]
  },
  {
    "first_name": "Michael",
    "middle_name": "Ong",
    "last_name": "Reyes",
    "birth_date": "March 28, 1960",
    "education": null // <-- this caused the error
  },

  ...
]

因此,我通过添加?将该属性声明为可空类型,它解决了该问题。

@SerializedName("education") val education: List<Education>?

根据您的情况,我认为您要解析的JSON具有空值会导致异常。