80

I have following class which reads and writes an array of objects from/to a parcel:

class ClassABC extends Parcelable {
    MyClass[] mObjList;

    private void readFromParcel(Parcel in) {
        mObjList = (MyClass[]) in.readParcelableArray(
                com.myApp.MyClass.class.getClassLoader()));
    }

    public void writeToParcel(Parcel out, int arg1) {
        out.writeParcelableArray(mObjList, 0);
    }

    private ClassABC(Parcel in) {
        readFromParcel(in);
    }

    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<ClassABC> CREATOR =
            new Parcelable.Creator<ClassABC>() {

        public ClassABC createFromParcel(Parcel in) {
            return new ClassABC(in);
        }

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

In above code I get a ClassCastException when reading readParcelableArray:

ERROR/AndroidRuntime(5880): Caused by: java.lang.ClassCastException: [Landroid.os.Parcelable;

What is wrong in above code? While writing the object array, should I first convert the array to an ArrayList?

UPDATE:

Is it OK to convert an Object array to an ArrayList and add it to parcel? For instance, when writing:

    ArrayList<MyClass> tmpArrya = new ArrayList<MyClass>(mObjList.length);
    for (int loopIndex=0;loopIndex != mObjList.length;loopIndex++) {
        tmpArrya.add(mObjList[loopIndex]);
    }
    out.writeArray(tmpArrya.toArray());

When reading:

    final ArrayList<MyClass> tmpList = 
            in.readArrayList(com.myApp.MyClass.class.getClassLoader());
    mObjList= new MyClass[tmpList.size()];
    for (int loopIndex=0;loopIndex != tmpList.size();loopIndex++) {
        mObjList[loopIndex] = tmpList.get(loopIndex);
    }

But now I get a NullPointerException. Is above approach is correct? Why it is throwing an NPE?

Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
User7723337
  • 11,857
  • 27
  • 101
  • 182

4 Answers4

164

You need to write the array using the Parcel.writeTypedArray() method and read it back with the Parcel.createTypedArray() method, like so:

MyClass[] mObjList;

public void writeToParcel(Parcel out) {
    out.writeTypedArray(mObjList, 0);
}

private void readFromParcel(Parcel in) {
    mObjList = in.createTypedArray(MyClass.CREATOR);
}

The reason why you shouldn't use the readParcelableArray()/writeParcelableArray() methods is that readParcelableArray() really creates a Parcelable[] as a result. This means you cannot cast the result of the method to MyClass[]. Instead you have to create a MyClass array of the same length as the result and copy every element from the result array to the MyClass array.

Parcelable[] parcelableArray =
        parcel.readParcelableArray(MyClass.class.getClassLoader());
MyClass[] resultArray = null;
if (parcelableArray != null) {
    resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
}
Michael
  • 53,859
  • 22
  • 133
  • 139
  • 1
    Thanks, that did the trick! This should be clarified in the documentation because the method naming makes it sound like a Parcelable array is what should be written, when that's clearly more code. – Tom Jan 18 '13 at 05:45
  • Yes, the name of the method is a little confusing. When I faced this problem, the only way to solve it was reading Android sources, that's why I decided to write this answer. – Michael Jan 18 '13 at 07:31
  • 4
    By the way, the statements within the `if (parcelableArray != null) {...}` can be simplified to `resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);` – CrimsonX Jul 24 '13 at 19:37
  • 1
    @Michael: I think it should be "mObjList = new MyClass[size]; in.readTypedArray(mObjList, MyClass.CREATOR);" instead of "mObjList = in.readTypedArray(new MyClass[size], MyClass.CREATOR);", because in.readTypedArray has return type void, but otherwise your post has helped me a lot – Linard Arquint Jun 01 '15 at 10:11
  • 1
    @LinardArquint, actually the code sample was not very good. You should use `in.createTypedArray()` instead. Please check the updated example. – Michael Jun 01 '15 at 13:01
  • @Michael: I've tried out my suggestion and it looks like both versions work – Linard Arquint Jun 01 '15 at 20:47
  • @LinardArquint, yep, your variant works but it's longer and you write array's length twice. Also it doesn't work for `null` arrays. But these are minor points so just use what you do like more. – Michael Jun 02 '15 at 09:24
  • @Michael: I handle null arrays myself by writing -1 as length into the parcel, but thank you very much for your support – Linard Arquint Jun 02 '15 at 09:30
37

ERROR/AndroidRuntime(5880): Caused by: java.lang.ClassCastException: [Landroid.os.Parcelable;

According to the API, readParcelableArray method returns Parcelable array (Parcelable[]), which can not be simply casted to MyClass array (MyClass[]).

But now i get Null Pointer Exception.

It is hard to tell the exact cause without the detailed exception stack trace.


Suppose you have made MyClass implements Parcelable properly, this is how we usually do for serialize/deserialize a array of parcelable objects:

public class ClassABC implements Parcelable {

  private List<MyClass> mObjList; // MyClass should implement Parcelable properly

  // ==================== Parcelable ====================
  public int describeContents() {
    return 0;
  }

  public void writeToParcel(Parcel out, int flags) {
    out.writeList(mObjList);
  }

  private ClassABC(Parcel in) {
    mObjList = new ArrayList<MyClass>();
    in.readList(mObjList, getClass().getClassLoader());
   }

  public static final Parcelable.Creator<ClassABC> CREATOR = new Parcelable.Creator<ClassABC>() {
    public ClassABC createFromParcel(Parcel in) {
      return new ClassABC(in);
    }
    public ClassABC[] newArray(int size) {
      return new ClassABC[size];
    }
  };

}

Hope this helps.

You can also use the following methods:

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  private ClassABC(Parcel in) {
      mObjList = new ArrayList<ClassABC>();
      in.readTypedList(mObjList, ClassABC.CREATOR);
  }
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
yorkw
  • 40,926
  • 10
  • 117
  • 130
  • 6
    Shouldn't it be `MyClass.class.getClassLoader()` instead of `getClass().getClassLoader()`? Because I think the class loader is being used to find the Parceleable class that we are trying to read. – Aswin Kumar Oct 17 '12 at 03:52
  • exactly I dont want ClassABC, I want MyClass – Srneczek Nov 12 '15 at 18:48
12

I had a similar problem, and solved it this way. I defined a helper method in MyClass, for converting an array of Parcelable to an array of MyClass objects:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    MyClass[] objects = new MyClass[parcelables.length];
    System.arraycopy(parcelables, 0, objects, 0, parcelables.length);
    return objects;
}

Whenever I need to read a parcelable array of MyClass objects, e.g. from an intent:

MyClass[] objects = MyClass.toMyObjects(getIntent().getParcelableArrayExtra("objects"));

EDIT: Here is an updated version of the same function, that I am using more recently, to avoid compile warnings:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    if (parcelables == null)
        return null;
    return Arrays.copyOf(parcelables, parcelables.length, MyClass[].class);
}
Giorgio Barchiesi
  • 6,109
  • 3
  • 32
  • 36
5

You need to write the array using the Parcel.writeTypedArray() method and read it back with the Parcel.readTypedArray() method, like so:

MyClass[] mObjArray;

public void writeToParcel(Parcel out, int flags) {
    out.writeInt(mObjArray.length);
    out.writeTypedArray(mObjArray, flags);
}

protected MyClass(Parcel in) {
    int size = in.readInt();
    mObjArray = new MyClass[size];
    in.readTypedArray(mObjArray, MyClass.CREATOR);
}

For lists, you can do the following:

  ArrayList<MyClass> mObjList;

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  protected MyClass(Parcel in) {
      mObjList = new ArrayList<>(); //non-null reference is required
      in.readTypedList(mObjList, MyClass.CREATOR);
  }
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Can you show the code if `MyClass` is some `interface`? I am not sure about how to handle `MyClass.CREATOR` in this case. – isabsent Sep 28 '18 at 06:32
  • @isabsent if `MyClass` is interface, then its implementations must all implement `Parcelable` and have their own `CREATOR`s. – EpicPandaForce Sep 28 '18 at 09:25
  • @isabsent for your problem: I also have a `Set` of an interface type (lets name it `MyInterface`) that implements `Parcelable`, but no concrete `CREATOR`s. I used `dest.writeTypedList(new ArrayList<>(mySet))` to write into and `ArrayList data = (ArrayList) in.readArrayList(MyInterface.class.getClassLoader());` to read from `Parcel`. Disadvantage: there is an ugly warning for an unchecked cast. – Danny Jan 03 '20 at 12:00
  • @heisenberg theoretically if your class implements Parcelable correctly and handles subtypes properly, then each subtype has its CREATOR, and what Android does in this case is look up the CREATOR field on the actual class via reflection. – EpicPandaForce Jan 03 '20 at 12:02
  • @EpicPandaForce yes, every sub type of my interface (which implements Parcelable) has it's own `CREATOR`. But my Object that's get parcelled is holding a `Set` (simply typed to `MyInterface`). That `Set` can contain `MyInterfaceA`, `MyInterfaceB`, etc. As long my set must be typed to `MyInterface`, I can't use a `CREATOR`, or am I wrong? Maybe you have an example for me for this scenario? Using a BaseClass for all MyInterface with a `CREATOR` implementation would help, but so I require a new class only for enable that parcelling... Thank you! – Danny Jan 03 '20 at 12:13
  • Ah, in that case, you can use `putParcelableList`, but you do need the cast I think. – EpicPandaForce Jan 03 '20 at 12:26