3

Requirement: Find out if price is null. Since a primitive float data type cannot be checked for null since its always 0.0, I opted out to use Float instead, as it can be checked for null.

public class QOptions implements Parcelable {
    public String text;
    public Float price;
}

protected QOptions(Parcel in) {
    text = in.readString();
    unit_price = in.readFloat();
}

@Override
public void writeToParcel(Parcel parcel, int i) {
    parcel.writeString(this.text);
    parcel.writeFloat(this.price);
}

However, since the class also implements Parcelable, the writeToParcel crashes with the following exception:

Attempt to invoke virtual method 'float java.lang.Float.floatValue()' on a null object reference

And the exception points to this line:

parcel.writeFloat(this.price);

How can I use the Float data type along with writeToParcel and not cause the exception? Or is there a better way to accomplish my requirement? I just need the price to be null if it's null.

Jay
  • 4,873
  • 7
  • 72
  • 137
  • Off topic: Read [Why not use Double or Float to represent currency?](https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency) – Eugen Pechanec Aug 16 '17 at 15:27

3 Answers3

4

You can handle it in the below manner.

@Override
public void writeToParcel(Parcel dest, int flags) {
    if (price == null) {
        dest.writeByte((byte) (0x00));
    } else {
        dest.writeByte((byte) (0x01));
        dest.writeFloat(price);
    }
}

To read the value of float -

unit_price = in.readByte() == 0x00 ? null : in.readFloat();
Kapil G
  • 4,081
  • 2
  • 20
  • 32
  • Can you explain what the solution does? Is that the address of the object? – Jay Aug 16 '17 at 07:31
  • Also are there any changes to the `in.readFloat();`? cos I'm getting a new exception: `java.lang.RuntimeException: Parcel android.os.Parcel@b96a33e: Unmarshalling unknown type code 7274598 at offset 2536` – Jay Aug 16 '17 at 07:35
  • 1
    Its not the address, its just a null representation code byte that has been added. When you read it, you can do it in the updated manner. Just added it in my answer :) – Kapil G Aug 16 '17 at 07:37
  • Sweet solution! Works like a charm. Thanks :) – Jay Aug 16 '17 at 07:39
  • Happy to help :) – Kapil G Aug 16 '17 at 07:41
2

Decimal types have a number of special values: NaN, negative and positive infinities. You can use those values to indicate null:

if (price == null) {
  parcel.writeFloat(Float.NaN);
} else {
  parcel.writeFloat(price);
}

And when reading:

float p = parcel.readFloat();
if (Float.isNaN(p)) {
  price = null;
} else {
  price = p;
}

NaN means "not a number", so it kind-of fits thematically for serializing things.

Unlike the solution, provided by @Kapil G, this approach does not waste additional 4 bytes for nullability flag (each call to writeByte() actually stores entire int in Parcal for performance reasons).

user1643723
  • 4,109
  • 1
  • 24
  • 48
0

For parceling Float these two method calls are safe:

dest.writeValue(price);
in.readValue(null);

For parceling any parcelable type you can use this:

SomeType value; // ...
dest.writeValue(value);
in.readValue(SomeType.class.getClassLoader());

List of parcelable types can be found in Parcel docs.

Pros

  • One line for each read and write.
  • You don't have to worry about manually differentiating between null and a float when parceling and unparceling. It's done for you internally.
  • Can express NaN and infinities.

How it works

Here's the relevant part of Parcel source code:

public class Parcel {
    private static final int VAL_NULL = -1;
    private static final int VAL_FLOAT = 7;

    public final void writeValue(Object v) {
        if (v == null) {
            writeInt(VAL_NULL);
        } else if (v instanceof Float) {
            writeInt(VAL_FLOAT);
            writeFloat((Float) v);
        } // ...
    }

    public final Object readValue(ClassLoader loader) {
        int type = readInt();

        switch (type) {
            case VAL_NULL:
                return null;

            case VAL_FLOAT:
                return readFloat();

            // ...
        }
    }
}

Note that for Float (and other boxed primitives) the loader parameter is unused so you can pass null.

Explore the source here: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Parcel.java

Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124