Read & writing arrays of Parcelable objects - android

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?

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

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

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

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

Related

Parcelable object with user data types?

Hey guys ive found some tutorials about sending non primitive object to activity via intent. But see only that they have members of only primitive in all examples.
I have a class with members that are user data types.
How do i send an object with implementing Parcelable with non primitive instance variables like arraylist etc?
Thanks
The objects that are members of your class must also be Parcelable (or Serializable), and any objects they include must also be Parcelable (or Serializable). To summarize, a Parcelable object must have fields that are either: primitives, Parcelable objects (and their supported collections such as Map or ArrayList) or Serializable objects(and their supported collections such as Map or ArrayList).
A sample piece of code demonstrating this (the Foo class is a Parcelable which contains Bar, which is also Parcelable), is the following (in Java):
import android.os.Parcel;
import android.os.Parcelable;
public class Foo implements Parcelable {
private int primitive;
private Bar object;
public Foo() {
primitive = 0;
object = null;
}
private Foo(final Parcel in) {
primitive = in.readInt();
object = in.readParcelable(Bar.class.getClassLoader());
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(primitive);
dest.writeParcelable(object, flags);
}
public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() {
public Foo createFromParcel(Parcel in) {
return new Foo(in);
}
public Foo[] newArray(int size) {
return new Foo[size];
}
};
}
and the Bar class:
import android.os.Parcel;
import android.os.Parcelable;
public class Bar implements Parcelable {
private String attribute;
public Bar() {
attribute = "";
}
private Bar(final Parcel in) {
attribute = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(attribute);
}
public static final Parcelable.Creator<Bar> CREATOR = new Parcelable.Creator<Bar>() {
public Bar createFromParcel(Parcel in) {
return new Bar(in);
}
public Bar[] newArray(int size) {
return new Bar[size];
}
};
}

Can't debug a class that implements parcelable

If I have an object called "EditorialCollection" and it implements parcelable why can't I debug into the overrided methods like writeToParcel?
protected EditorialCollection(Parcel in) {
super(in);
//break point here:
items = new ArrayList<>();
in.readList(items, CollectionItem.class.getClassLoader());
embedPagination = (EmbedPagination) in.readValue(EmbedPagination.class.getClassLoader());
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<EditorialCollection> CREATOR = new Creator<EditorialCollection>() {
#Override
public EditorialCollection createFromParcel(Parcel in) {
return new EditorialCollection(in);
}
#Override
public EditorialCollection[] newArray(int size) {
return new EditorialCollection[size];
}
};
I can't hit break points nor can I output logging there. Am I doing something wrong or can someone explain why logging/breakpoints won't work here.
Thanks,
Reid
Parcel objects or Parcelable implementation objects cannot being debugged.
public final class Parcel {
private static final boolean DEBUG_RECYCLE = false;
private static final boolean DEBUG_ARRAY_MAP = false;
private static final String TAG = "Parcel";
.
.
.
}
You can use Parcelables.forceParcel(builder, CREATOR) method for testing Parcel classes.
But it is only available for Test Classes.
Also you can copy this func from Parcelables class. And you can try in java code:
public static <T extends Parcelable> T forceParcel(T parcelable, Creator<T> creator) {
Parcel parcel = Parcel.obtain();
try {
parcelable.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return creator.createFromParcel(parcel);
} finally {
parcel.recycle();
}
}

Android Parcelable write / read String List null object reference [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 6 years ago.
I'm trying to read a List of Strings and I keep having the following error:
with the following Parcelable class:
import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
public class ActivePolicies implements Parcelable {
private List<String> activePolicies;
public ActivePolicies(List<String> activePolicies) {
this.activePolicies = activePolicies;
}
public List<String> getActivePolicies(){
return activePolicies;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringList(activePolicies);
}
public static final Parcelable.Creator<ActivePolicies> CREATOR = new Parcelable.Creator<ActivePolicies>() {
#Override
public ActivePolicies createFromParcel(Parcel in) {
try {
return new ActivePolicies(in.createStringArrayList());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
#Override
public ActivePolicies[] newArray(int size) {
return new ActivePolicies[size];
}
};
}
It keeps giving me the exception on createFromParcel(), but I can't figure out why.
It seems a simple parcelable, but when I'm debugging at readStringList() from the Parcel class, the list is null.
But when I evaluate the value in createStringArrayList() before it's returning, the list is there with the expected values.
you need to create constructor that accept Parcel
public ActivePolicies(Parcel in) {
in.readStringList(activePolicies);
}
in.createStringArrayList()? Why are you creating a new String list when you want to construct your object?
Use this method.
Parcel.readStringList(List<String> list)
Read into the given List items String objects that were written with
writeStringList(List) at the current dataPosition().
Recommended: implement a constructor that accepts a Parcel, then, optionally a readFromParcel method.
public class ActivePolicies implements Parcelable {
private List<String> activePolicies;
public ActivePolicies() {
activePolicies = new ArrayList<String>();
}
public ActivePolicies(Parcel in) {
this();
readFromParcel(in);
}
private void readFromParcel(Parcel in) {
in.readStringList(activePolicies);
}
In the creator.
return new ActivePolicies(in);

How to make nested ArrayLists parcelable

To pass an arrayList of objects to a fragment, I have to make the list objects parcelable.
public mObjectClass implements Parcelable {
// Some code
}
The problem is that one of the attributes in my list objects is another object-based arrayList.
public mObjectClass implements Parcelable {
// Some code
private ArrayList<someOtherObject> anotherArrayList;
}
How can I make mObjectClass parcelable?
someOtherObject has to implement Parcelable (not extend has in your question) too. Then you can call parcel.writeTypedList(anotherArrayList); to write it and parcel.readTypedList(yourList, someOtherObject.CREATOR) to read it back. You can read more here
This solution is heavily influenced by some Stackoverflow posts, including
this.
Essentially make both Classes parcelable and make use of someOtherClasses Parcelable.Creator.
mObjectClass:
public class mObjectClass implements Parcelable {
private ArrayList<someOtherObject> anotherArrayList;
//add getter + setter...
public mObjectClass() {
anotherArrayList = new ArrayList<someOtherObject>();
}
public mObjectClass(Parcel in) {
anotherArrayList = new ArrayList<someOtherObject>();
in.readTypedList(anotherArrayList, someOtherObject.CREATOR);
}
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel outParcel, int flags) {
outParcel.writeTypedList(anotherArrayList);
}
public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
#Override
public mObjectClass createFromParcel(Parcel in) {
return new mObjectClass(in);
}
#Override
public mObjectClass[] newArray(int size) {
return new mObjectClass[size];
}
};
}
someOtherObject:
public class someOtherObject implements Parcelable {
String someString;
//add getter + setter...
public void writeToParcel(Parcel out, int flags) {
out.writeString(someString);
}
public static final Parcelable.Creator<someOtherObject> CREATOR = new Parcelable.Creator<someOtherObject>() {
public someOtherObject createFromParcel(Parcel in) {
return new someOtherObject(in);
}
public someOtherObject[] newArray(int size) {
return new someOtherObject[size];
}
};
public int describeContents() {
return 0;
}
public someOtherObject(Parcel in) {
someString = in.readString();
}
public someOtherObject() {
}
public someOtherObject(String someString) {
this.someString = someString;
}
}
Tada, you are now able to add mObjectClass as extra to your intents after initialising it and using a setter to set the Arraylist with other someOtherObjects.

Problem in implementing Parcelable containing other Parcelable

I'm implementing Parcelable class that has another Parcelable insde.
In OuterParcelable class:
#Override
public void writeToParcel(Parcel dest, int flags) {
Bundle tmp = new Bundle();
tmp.putParcelable("innerParcelable", mParcelable);
dest.writeBundle(tmp);
and then:
public OuterParcelable(Parcel parcel) {
super();
Bundle b = parcel.readBundle();
mParcelable = b.getParcelable("innerParcelable");
and:
public OuterParcelable createFromParcel(Parcel in) {
return new OuterParcelable(in);
}
When I recreate object using above code I get:
08-18 17:13:08.566: ERROR/AndroidRuntime(15520): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: my.package.InnerParcelable
A clean way to store non-primitive attributes as parcelable, possibly null, values. Use Parcel.writeValue() and readValue(). See comments in code below:
public class MyParcelableClass implements Parcelable {
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeValue(getIntegerAttribute()); // getIntegerAttribute() returns Integer
dest.writeValue(getDoubleAttribute());
dest.writeValue(getMyEnumAttribute()); // getMyEnumAttribute() returns a user defined enum
dest.wrtieValue(getUserClassAttribute()); //UserClass must implement Parcelable in a similar fashion
}
private MyParcelableClass(Parcel in) {
setIntegerAttribute((Integer)in.readValue(null)); //pass null to use default class loader. Ok for Integer, String, etc.
setDoubleAttribute((Double)in.readValue(null)); //Cast to your specific attribute type
setEnumAttribute((MyEnum)in.readValue(null));
setUserClassAttribute((UserClass)in.readValue(UserClass.class.getClassLoader())); //Use specific class loader
}
#Override
public int describeContents() ...
public static final Parcelable.Creator<ParcelableLocationBean> CREATOR ...
}
Works like a charm. writeValue() and readValue() encapsulate the dealing with possible nulls and type detection. From javadoc:
public final void writeValue (Object v) Flatten a generic object
in to a parcel. The given Object value may currently be one of the
following types: null, String, Integer, ... String[],
boolean[], ... Any object that implements the Parcelable protocol. ...
Why are you putting the value into a Bundle? Did you completely implement the parcelable in your class?
Parcelable Skeleton
public MyClass(Parcel in) {
readFromParcel(in);
}
//
// Parcelable Implementation
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(aParcelableClass, flags);
}
private void writeObject(Parcel dest, Object obj) {
if (obj != null) {
dest.writeInt(1);
dest.writeValue(obj);
} else {
dest.writeInt(0);
}
}
public void readFromParcel(Parcel in) {
aParcelableClass = in.readParcelable(ParcelableClass.class.getClassLoader());
}
private Object readObject(Parcel in) {
Object value = null;
if (in.readInt() == 1) {
value = in.readValue(null); // default classloader
}
return value;
}
public static final Parcelable.Creator<MyClass> CREATOR = new Parcelable.Creator<MyClass>() {
#Override
public MyClass createFromParcel(Parcel source) {
return new MyClass(source);
}
#Override
public MyClass[] newArray(int size) {
return new MyClass[size];
}
};
I added a few things to make null values more easily dealt with, but the principle is the same. You need the #Override items, constructor, and Creator.
If you're going to read and write a parcelable you will have issues if you specify null as the class loader.

Categories

Resources