Read & writing arrays of Parcelable objects

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?


Solution 1:

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);
}

Solution 2:

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);
  }

Solution 3:

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);
}

Solution 4:

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);
  }