Parcelable, what is newArray for? - android

I am implementing Parcelable in order to transmit some simple data throughout an Intent.
However, There is one method in the Parcelable interface that I don't understand at all : newArray().
It does not have any relevant documentation & is not even called in my code when I parcel/deparcel my object.
Sample Parcelable implementation :
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
So, my question is : what is this method for ? and when is it called ?
Is there any point in doing something else than return new MyParcelable[size]; in that method ?

this is a function to be called when you try to deserialize an array of Parcelable objects and for each single object createFromParcel is called.

It is there to prepare the typed array without all the generics stuff. That's it.
Returning just the standard return new MyParcelable[size]; is fine.
It is normal, that you never call it yourself. However, by calling something like Bundle.getParcelableArray() you end up in this method indirectly.

newArray is responsible to create an array of our type of the appropriate size

Related

Passing variables in and out of GCM task service

I am using the GCM network manager and I want to pass the service (specifically to the onRunTask(TaskParams taskParams) some objects. From the documentation taskParams are simply a string and a bundle but I want to pass more complex objects.
How can this be done?
Thank you!
One way is to have your custom object implement the Parcelable interface and use Bundle.putParcelable/Bundle.getParcelable.
It requires a little more effort to use than using Java's native serialization, but it's way faster (and I mean way, WAY faster).
For example:
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
Also you can read Parcelable vs Serializable

Using Parcelable to pass highly nested classes between Activities

Suppose I want to store a custom object of type MyObject in an Intent. The way to do this is to make MyObject implement Parcelable. If one of the fields of MyObject is also a custom object of type Widget the obvious thing to do is to make Widget implement Parcelable too.
The trouble is that there is a huge amount of boilerplate involved when implementing Parcelable. You can get around this by not making Widget implement Parcelable but instead just giving it a constructor taking a Parcel and a method writeToParcel as follows:
public final class Widget {
private final int a;
private final String b;
Widget(Parcel in) {
a = in.readInt();
b = in.readString();
}
void writeToParcel(Parcel out) {
out.writeInt(a);
out.writeString(b);
}
}
You can then have a Widget field in a Parcelable object as follows:
public class MyObject implements Parcelable {
private final int x;
private final Widget w;
MyObject(int x, Widget w) {
this.x = x;
this.w = w;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(x);
w.writeToParcel(out);
}
public static final Parcelable.Creator<MyObject> CREATOR
= new Parcelable.Creator<MyObject>() {
#Override
public MyObject createFromParcel(Parcel in) {
return new MyObject(in.readInt(), new Widget(in));
}
#Override
public MyObject[] newArray(int size) {
return new MyObject[size];
}
};
}
Is this an acceptable approach? Is it considered unidiomatic android to have many custom classes in a project that can be written to and read from Parcels without them actually implementing Parcelable? Or does the fact that I am using a Parcelable to pass complex objects with many fields of custom types (which in turn have many fields of custom type etc etc), indicate that I shouldn't be using Parcelable in the first place?
I would (and did) go with Parceler: https://github.com/johncarl81/parceler
Parceler is a code generation library that generates the Android
Parcelable boilerplate source code. No longer do you have to implement
the Parcelable interface, the writeToParcel() or createFromParcel() or
the public static final CREATOR. You simply annotate a POJO with
#Parcel and Parceler does the rest.
It's really easy to use.
It is recommended to use Parcelable when dealing with passing custom Objects through intents in Android. There isn't an "easy" work around. Since you are dealing with just one extra level of a custom Object (Widget), I would recommend you make Widget Parcelable also. You can also check out this link to see why it is the better approach than using default Serialization. https://coderwall.com/p/vfbing/passing-objects-between-activities-in-android
If your classes are beans, the best solution is the accepted one. If not, I have found that you can (slightly) reduce the pain of implementing Parcelable by creating abstract classes ParcelablePlus and CreatorPlus like this.
ParcelablePlus:
abstract class ParcelablePlus implements Parcelable {
#Override
public final int describeContents() {
return 0;
}
}
CreatorPlus:
abstract class CreatorPlus<T extends Parcelable> implements Parcelable.Creator<T> {
private final Class<T> clazz;
CreatorPlus(Class<T> clazz) {
this.clazz = clazz;
}
#Override
#SuppressWarnings("unchecked")
public final T[] newArray(int size) {
// Safe as long as T is not a generic type.
return (T[]) Array.newInstance(clazz, size);
}
}
Then the Widget class becomes:
public final class Widget extends ParcelablePlus {
private final int a;
private final String b;
Widget(int a, String b) {
this.a = a;
this.b = b;
}
#Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(a);
out.writeString(b);
}
public static final Creator<Widget> CREATOR = new CreatorPlus<Widget>(Widget.class) {
#Override
public Widget createFromParcel(Parcel in) {
return new Widget(in.readInt(), in.readString());
}
};
}

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

Better way to pass data between Activities in Android

In my application I am fetching data from JavaScript, as it is not possible to return the data as an array or object, I am returning it as a String.
Now to organize the data I am creating a class which contains ArrayLists and other string variables and further I am creating array of my class objects variable to store multiple records.
public class Data {
ArrayList<String> m_empArrayList = new ArrayList();
ArrayList<String> m_depArrayList = new ArrayList();
String m_time;
String m_duration;
}
Data d = new Data();
What would be a good approach to pass the data between Activities? As Intents and ShredPrefrences are used to pass small units of data I am not considering it here.
Implement the Parcelable interface in your custom object and transmit it via an Intent.
Here is an example of a Parcelable object.
public class MyObject implements Parcelable {
private String someString = null;
private int someInteger = 0;
public MyObject() {
// perform initialization if necessary
}
private MyObject(Parcel in) {
someString = in.readString();
someInteger = in.readInt();
}
public static final Parcelable.Creator<MyObject> CREATOR =
new Parcelable.Creator<MyObject>() {
#Override
public MyObject createFromParcel(Parcel source) {
return new MyObject(source);
}
#Override
public MyObject[] newArray(int size) {
return new MyObject[size];
}
};
// Getters and setters
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(someString);
dest.writeInt(someInteger);
}
}
Here is what happens. If you implement the Parcelable interface you have to create a private constructor which takes a Parcel as a parameter. That Parcel holds all the serialized values.
You must implement the nested class Parcelable.Creator with the name CREATOR as this is going to be called by Android when recreating your object.
The method describeContents() is only of use in special cases. You can leave it as it is with a return value of 0.
The interesting action happens in writeToParcel() where you, as the name tells, write your data to a Parcel object.
Now you can just add your custom object directly to an Intent like in this example.
MyObject myObject = new MyObject();
Intent i = new Intent();
i.setExtra("MY_OBJECT", myObject);
// implicit or explicit destination declaration
startActivity(i);
you can use Application class present in Android to pass data between activities.
here is a good link..http://www.helloandroid.com/tutorials/maintaining-global-application-state

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