1

I recently migrate my Java Models class to Kotlin Data class. I'm using @Parcelize annotation to avoid boilerplate code for Parcelable. My data class in Kotlin is shown below,

@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`

But it gives me the Null Pointer Error as shown below,

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)

I don't know why it shows NullPointerException as Java Model was working fine. How do I fix it and what is the reason behind of NPE ?

miPlodder
  • 795
  • 8
  • 18

3 Answers3

5

I've encountered this issue and found that one of the non-nullable property on my data class receives a null value on the json.

My data class:

@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
  },

  ...
]

So I declared that property as nullable type by adding ? and it resolves the issue.

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

In your situation, I believe that the JSON that you're trying to parse has a null value causing the exception.

mgcaguioa
  • 1,443
  • 16
  • 19
2

GSON uses unsafe API to construct your object - It's able to put nulls where you declared non-nullable types. The generated Parcelable implementation expects non-null values. It crashes when you try to put your object to parcels.

I see two options:

1) Adjust values manually after getting an instance

You declared all your properties as var. You could make an extension method that will fix non-null values for you.

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

Example:

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

Or if you had an immutable type with val properties:

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

Example:

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

The downside is you have to think about this and remember to call it on each object you get from GSON.

Both of these variants have other issues (memory consumption, concurrency).

2) Use serialization library built with Kotlin in mind

You can use Moshi Kotlin Codegen. Moshi is a serialization library just like GSON but its Kotlin Codegen annotation processor will generate special JSON adapters for your types that will throw exceptions if you try to deserialize nulls into non-nullable properties.

It will throw exception as soon as you try to deserialize invalid JSON, not when you try to use your Kotlin/Java objects.

Disclaimer: I've only used Moshi + Kotshi which works on the same principle.

I'm going to leave the implementation to you as an excercise.

Food for thought

You shouldn't put large objects or large amount of objects to Parcel/Intent. There's a size limit. Perhaps you should use a database.

Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124
  • The cause of error you mentioned is correct. In method 1, where do I have to write this and how do I call these in @Parcelize Annotated Class ? – miPlodder Aug 01 '18 at 04:12
  • @miPlodder Remember to call it on each object you get from GSON. – Eugen Pechanec Aug 01 '18 at 06:22
  • I'm not able to understand the implementation that you have given in Method-1. – miPlodder Aug 01 '18 at 16:59
  • @miPlodder After you get your `TimeLine` objects from GSON you have to call `fixNulls` on each of them. I really recommend the second option so instead you get exceptions when you get unexpected JSON. Then fix your server, so it sends you correct responses (with empty arrays instead of nulls). – Eugen Pechanec Aug 01 '18 at 17:10
1
@Parcelize            
data class TimeLine(

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

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

Your data model class looks similar with this You need to add ? (Null safety) operator so that it will accept null values. For more details about Null safety go through Kotlin official documentation

shahid17june
  • 1,441
  • 1
  • 10
  • 15
  • The cause for Error is that API returns null for fields, which @Parcelize is not able to handle. Since, I don't have much knowledge about Kotlin. Can you tell me, how it can be resolved ? – miPlodder Aug 01 '18 at 04:04
  • @miPlodder Can you please show your full Logcat error – shahid17june Aug 04 '18 at 04:30