2

I've implemented parcelable for my custom class:

public class Tag implements Parcelable {
    int id;
    int type;
    int proficiencyLevel;
    HashMap<String, String> translations;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeInt(type);
        dest.writeInt(proficiencyLevel);

        // if this part is uncommented I get a NullPointerException
        /*
        dest.writeInt(translations.size());
        for (HashMap.Entry<String, String> entry : translations.entrySet()) {
            dest.writeString(entry.getKey());
            dest.writeString(entry.getValue());
        }
        */
    }

    public static final Parcelable.Creator<Tag> CREATOR = new Parcelable.Creator<Tag>() {
        public Tag createFromParcel(Parcel in) {
            return new Tag(in);
        }

        public Tag[] newArray(int size) {
            return new Tag[size];
        }
    };

    private Tag(Parcel in) {
        id = in.readInt();
        type = in.readInt();
        proficiencyLevel = in.readInt();

        // I've put breakpoint here and it seems to work OK, but then I get a NullPointerException
        // deserialize `translations` field
        /*int size = in.readInt();
        for (int i = 0; i < size; i++) {
            String key = in.readString();
            String value = in.readString();
            translations.put(key, value);
        }*/
    }
}

Now I pass it like this from activity A:

Tag[] tagsList = new Gson().fromJson(tags, Tag[].class);
Intent activityTagsSelectorLauncher = new Intent(this, ActivityTagsSelector.class);
activityTagsSelectorLauncher.putParcelableArrayListExtra("tagsList", new ArrayList<>(Arrays.asList(tagsList)));
startActivityForResult(activityTagsSelectorLauncher, SELECT_TAGS_REQUEST);

And receive it like so in activity B:

ArrayList<Tag> tagsList = getIntent().getParcelableArrayListExtra("tagsList");

It's all working OK if I don't write HashMap<String, String> translations field. I get NullPointerException when activity B starts. I've put breakpoint inside constructor private Tag(Parcel in) { and there it seems that everything works fine so I don't understand what I'm doing wrong.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488

2 Answers2

3

You get NPE, because you don't initialize your translations Map:

HashMap<String, String> translations

is like

HashMap<String, String> translations = null;

Change declaration and initialize the map:

HashMap<String, String> translations = new HashMap<String, String>();

After you can use it uncommenting the lines

dest.writeInt(translations.size());
for (HashMap.Entry<String, String> entry : translations.entrySet()) {
    dest.writeString(entry.getKey());
    dest.writeString(entry.getValue());
}

UPDATE: to clarify what is happening, you declare variable and do not initialize it, so JVM does, and translates into HashMap<String, String> translations = null.

Initial Values of Variables

Every variable in a program must have a value before its value is used:

Each class variable, instance variable, or array component is initialized with a default value when it is created

[...] For all reference types, the default value is null.

Take a look at this default initialization table:

Type        Default Value
---------   -------------
boolean     false
byte        (byte) 0
short       (short) 0
int         0
long        0L
char        \u0000
float       0.0f
double      0.0d
object      null         <-------------- YAY!

After, when you try to get size with translations.size() is same as null.size(), there is your NPE...

Another scenario in a bigger code, is you may not want to initialize translations, so you can safe-check your sentence by:

dest.writeInt(translations != null ? translation.size() : 0);

For further info take a look here, here, and HERE

Community
  • 1
  • 1
Jordi Castilla
  • 26,609
  • 8
  • 70
  • 109
  • Thanks, indeed that was the error. Can you please add a bit more about what happened during parceling and how it led to NullPointerException? – Max Koretskyi May 20 '15 at 09:06
  • thanks, but `when you try to get size with translations.size()` - here the translations should not be `null`, since here `Tag[] tagsList = new Gson().fromJson(tags, Tag[].class);` they are populated with values, or they not? – Max Koretskyi May 20 '15 at 11:30
  • **You cannot populate a `null` object**, populate does not mean create and fill, it only means fill... You need to populate an empty (or already existing) object... – Jordi Castilla May 20 '15 at 12:27
  • Ah, I see, so basically `fromJson` didn't populate `translations` object, but it wasn't obvious until I requested it, correct? – Max Koretskyi May 20 '15 at 12:33
  • 1
    right! you may need to check if null and then create and populate, or maybe populate ONLY if is empty and not null.... there are plenty of possibilities and JVM only does what you say to *her* ;) – Jordi Castilla May 20 '15 at 12:37
  • I see, thanks a lot! I thought that `fromJson` method creates objects and populates them and not just populate existing ones... – Max Koretskyi May 20 '15 at 12:39
0

null error => due to missing initialization. Please initialize your hashmap.

Assume that you are passing this parcel ActivityA to ActivityB

I will share full code for those people who are having difficulty passing hashmap

Parcelable - Tag.java

    public class Tag implements Parcelable {
    int id;
    int type;
    int proficiencyLevel;
    HashMap<String, String> translations = new HashMap<String,String>(); // initialize
...
 @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeInt(type);
        dest.writeInt(proficiencyLevel);

      //add below 
       dest.writeInt(translations.size());
        for (String s: translations.keySet()) {
            dest.writeString(s);
            dest.writeString(records.get(s));
        }

    }

...

private Tag(Parcel in) {
        id = in.readInt();
        type = in.readInt();
        proficiencyLevel = in.readInt();

        //add below 
        int count = in.readInt();
        for (int i = 0; i < count; i++) {
            translations.put(in.readString(), in.readString());
        }

    }
...
   //same as your code
    public static final Parcelable.Creator<Tag> CREATOR = new Parcelable.Creator<Tag>() {
        public Tag createFromParcel(Parcel in) {
            return new Tag(in);
        }

        public Tag[] newArray(int size) {
            return new Tag[size];
        }
    };
}

Activity A

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class ActivityA extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ParcelTest p = new ParcelTest();
        p.put("green", "go");
        p.put("yellow", "wait");
        p.put("red", "stop");

        Bundle b = new Bundle();
        b.putParcelable("com.example.trafficlight", p);

        Intent i = new Intent(this, ActivityB.class);
        i.putExtras(b);

        startActivity(i);
    }
}

Activity B

public class ActivityB extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle b = getIntent().getExtras();

        ParcelTest p = b.getParcelable("com.example.trafficlight");

        String red = p.get("red");
        // ...
    }
}

Credit http://blog.cluepusher.dk/2009/10/28/writing-parcelable-classes-for-android/

Energy
  • 940
  • 13
  • 20