I've got a few classes that implement Parcelable and some of these classes contain each other as properties. I'm marshalling the classes into a Parcel to pass them between activities. Marshalling them TO the Parcel works fine, but when I try to unmarshall them I get the following error:
...
AndroidRuntime E Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: schemas.Arrivals.LocationType
AndroidRuntime E at android.os.Parcel.readParcelable(Parcel.java:1822)
AndroidRuntime E at schemas.Arrivals.LayoverType.<init>(LayoverType.java:121)
AndroidRuntime E at schemas.Arrivals.LayoverType.<init>(LayoverType.java:120)
AndroidRuntime E at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.java:112)
AndroidRuntime E at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.java:1)
AndroidRuntime E at android.os.Parcel.readTypedList(Parcel.java:1509)
AndroidRuntime E at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.java:244)
AndroidRuntime E at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.java:242)
AndroidRuntime E at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.java:234)
AndroidRuntime E at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.java:1)
...
The LayoverType class (where it's failing):
public class LayoverType implements Parcelable {
protected LocationType location;
protected long start;
protected long end;
public LayoverType() {}
public LocationType getLocation() {
return location;
}
public void setLocation(LocationType value) {
this.location = value;
}
public long getStart() {
return start;
}
public void setStart(long value) {
this.start = value;
}
public long getEnd() {
return end;
}
public void setEnd(long value) {
this.end = value;
}
// **********************************************
// for implementing Parcelable
// **********************************************
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(location, flags);
dest.writeLong(start);
dest.writeLong(end );
}
public static final Parcelable.Creator<LayoverType> CREATOR = new Parcelable.Creator<LayoverType>() {
public LayoverType createFromParcel(Parcel in) {
return new LayoverType(in);
}
public LayoverType[] newArray(int size) {
return new LayoverType[size];
}
};
private LayoverType(Parcel dest) {
location = (LocationType) dest.readParcelable(null); // it's failing here
start = dest.readLong();
end = dest.readLong();
}
}
Here's the LocationType class:
public class LocationType implements Parcelable {
protected int locid;
protected String desc;
protected String dir;
protected double lat;
protected double lng;
public LocationType() {}
public int getLocid() {
return locid;
}
public void setLocid(int value) {
this.locid = value;
}
public String getDesc() {
return desc;
}
public void setDesc(String value) {
this.desc = value;
}
public String getDir() {
return dir;
}
public void setDir(String value) {
this.dir = value;
}
public double getLat() {
return lat;
}
public void setLat(double value) {
this.lat = value;
}
public double getLng() {
return lng;
}
public void setLng(double value) {
this.lng = value;
}
// **********************************************
// for implementing Parcelable
// **********************************************
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt (locid);
dest.writeString(desc );
dest.writeString(dir );
dest.writeDouble(lat );
dest.writeDouble(lng );
}
public static final Parcelable.Creator<LocationType> CREATOR = new Parcelable.Creator<LocationType>() {
public LocationType createFromParcel(Parcel in) {
return new LocationType(in);
}
public LocationType[] newArray(int size) {
return new LocationType[size];
}
};
private LocationType(Parcel dest) {
locid = dest.readInt ();
desc = dest.readString();
dir = dest.readString();
lat = dest.readDouble();
lng = dest.readDouble();
}
}
Update 2: As far as I can tell it's failing at the following bit of code (from Parcel's source):
Class c = loader == null ? Class.forName(name) : Class.forName(name, true, loader);
Why is it not able to find the class? It both exists and implements Parcelable.
Because this was not answered in "answer" but in comment I will post an answer:
As #Max-Gontar pointed you should use LocationType.class.getClassLoader() to get the correct ClassLoader and get rid of ClassNotFound exception, i.e.:
in.readParceleable(LocationType.class.getClassLoader());
I had the same problem with the following setup: some handler creates a Message and sends its over a Messenger to a remote service.
the Message contains a Bundle where I put my Parcelable descendant:
final Message msg = Message.obtain(null, 0);
msg.getData().putParcelable("DOWNLOADFILEURLITEM", downloadFileURLItem);
messenger.send(msg);
I had the same exception when the remote service tried to unparcel. In my case, I had overseen that the remote service is indeed a separate os process. Therefore, I had to set the current classloader to be used by the unparcelling process on the service side:
final Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());
DownloadFileURLItem urlItem = (DownloadFileURLItem)
bundle.getParcelable("DOWNLOADFILEURLITEM");
Bundle.setClassLoader sets the classloader which is used to load the appropriate Parcelable classes. In a remote service, you need to reset it to the current class loader.
I found the problem was I was not passing my applications ClassLoader to the unmarshalling function:
in.readParceleable(getContext().getClassLoader());
Rather than:
in.readParceleable(null);
OR
in.readParceleable(MyClass.class.getClassLoader());
Just adding my 2 cents here, because I lost more than half a day scratching my head on this. You might get this error if you don't put the writes methods and reads methods in the exact same order. For instance the following would be wrong:
#Override
// Order: locid -> desc -> lat -> dir -> lng
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt (locid);
dest.writeString(desc);
dest.writeDouble(lat);
dest.writeString(dir);
dest.writeDouble(lng);
}
// Order: locid -> desc -> dir -> lat -> lng
private LocationType(Parcel dest) {
locid = dest.readInt();
desc = dest.readString();
dir = dest.readString();
lat = dest.readDouble();
lng = dest.readDouble();
}
By the way the author did this correctly but it might help someone one day.
I am not very familiar with Parcelable but if it's anything like Serialization each call to write an object that implements the interface will cause a recursive call to writeToParcel(). Therefore, if something along the call stack fails or writes a null value the class that initiated the call may not be constructed correctly.
Try:
Trace the writeToParcel() call stack through all the classes starting at the first call to writeToParcel() and verify that all the values are getting sent correctly.
I got ClassNotFoundException too and posting my solution bc the answers here led me to the right direction. My scenario is that I have nested parcelable objects. Object A contains an ArrayList of Object's B. Both implement Parcelable.
Writing the list of B Object's in class A:
#Override
public void writeToParcel(Parcel dest, int flags) {
...
dest.writeList(getMyArrayList());
}
Reading the list in class A:
public ObjectA(Parcel source) {
...
myArrayList= new ArrayList<B>();
source.readList(myArrayList, B.class.getClassLoader());
}
Thank you!
Instead of using writeParcelable and readParcelable use writeToParcel and createFromParcel directly. So the better code is:
#Override
public void writeToParcel(Parcel dest, int flags) {
location.writeToParcel(dest, flags);
dest.writeLong(start);
dest.writeLong(end );
}
public static final Parcelable.Creator<LayoverType> CREATOR = new Parcelable.Creator<LayoverType>() {
public LayoverType createFromParcel(Parcel in) {
return new LayoverType(in);
}
public LayoverType[] newArray(int size) {
return new LayoverType[size];
}
};
private LayoverType(Parcel dest) {
location = LocationType.CREATOR.createFromParcel(dest);
start = dest.readLong();
end = dest.readLong();
}
Well I had the same problem and solve it in a very silly way that I dont know if its called a solution at all.
lets say you have this class you want to pass to another activity
public class Person implements Parcelable,Serializable{
public String Name;
public int Age;
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public SeriesInfo(Parcel in) {
age= in.readInt(); //her was my problem as I have put age befor name
//while in the writeToParcel function I have defined
//dest.writeInt(age) after in.readString();???!!!!
name= in.readString();
}
}
Thats it When I changed the:
dest.writeString(name);
dest.writeInt(age);
to
dest.writeInt(age);
dest.writeString(name);
The problem was solved???!!!!
If you have an Object with a property of type of List objects you should pass the class loader when you read the property for example:
public class Mall implements Parcelable {
public List<Outstanding> getOutstanding() {
return outstanding;
}
public void setOutstanding(List<Outstanding> outstanding) {
this.outstanding = outstanding;
}
protected Mall(Parcel in) {
outstanding = new ArrayList<Outstanding>();
//this is the key, pass the class loader
in.readList(outstanding, Outstanding.class.getClassLoader());
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeList(outstanding);
}
public static final Parcelable.Creator<Mall> CREATOR = new Parcelable.Creator<Mall>() {
public Mall createFromParcel(Parcel in) {
return new Mall(in);
}
public Mall[] newArray(int size) {
return new Mall[size];
}
};
}
Note: Is important that the class Outstanding implements the Parceable interface.
I got this exception because I was missing a constructor. The same must be done for all classes that implement Parcelable:
// add new constructor
#RequiresApi(Build.VERSION_CODES.N)
private LocationType(Parcel dest, ClassLoader loader) {
super(dest, loader);
locid = dest.readInt();
desc = dest.readString();
dir = dest.readString();
lat = dest.readDouble();
lng = dest.readDouble();
}
public static final Creator<LayoverType> CREATOR = new ClassLoaderCreator<LayoverType>() {
// add createFromParcel method with ClassLoader
#Override
public LayoverType createFromParcel(Parcel in, ClassLoader loader)
{
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? new LayoverType(in, loader) : new LayoverType(in);
}
public LayoverType createFromParcel(Parcel in) {
// call other createFromParcel method.
return createFromParcel(in, null);
}
public LayoverType[] newArray(int size) {
return new LayoverType[size];
}
};
Related
I'm trying to pass an ArrayList of unknown class type that extend an abstract class, to another activity using Parcelable. Since its not possible to use Parcelable.CREATOR on the abstract class, there is an error when I try to create the ArrayList: in.readTypedList(AbstractChannel.CREATOR), see below:
public class TvNetwork implements Parcelable {
public String name;
public ArrayList<? extends AbstractChannel> mChannels;
public TvNetwork(String name, ArrayList<? extends AbstractChannel> channels) {
this.name = name;
this.mChannels = channels;
}
protected TvNetwork(Parcel in) {
name = in.readString();
mChannels = in.readTypedList(AbstractChannel.CREATOR); // here is the error
}
public static final Creator<TvNetwork> CREATOR = new Creator<TvNetwork>() {
#Override
public TvNetwork createFromParcel(Parcel in) {
return new TvNetwork(in);
}
#Override
public TvNetwork[] newArray(int size) {
return new TvNetwork[size];
}
};
public ArrayList<? extends AbstractChannel> getChannels() {
return mChannels;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeTypedList(mChannels);
}
}
Writing seems to work but not reading. This obviously does not work either, but explains a bit more what I want to do:
in.readTypedList(mChannels, <? extends AbstractChannel>.class.getClassLoader());
Any ideas?
I am trying to pass 'Response' class object using IPC in AIDL. I have made the class parcelable:
public class Response implements Parcelable{
private long id;
private String speechString;
private List<String> responseString = new ArrayList<String>();
//set
...
}
//get
...
public Response(Parcel in) {
id = in.readLong();
speechString = in.readString();
if (in.readByte() == 0x01) {
responseString = new ArrayList<String>();
in.readList(responseString, String.class.getClassLoader());
} else {
responseString = null;
}
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(speechString);
if (responseString == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(responseString);
}
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Response createFromParcel(Parcel in) {
return new Response(in);
}
public Response[] newArray(int size) {
return new Response[size];
}
};
}
Defined Response.aidl:
package com.example;
parcelable Response;
IappMain.aidl is used for IPC and is defined as following:
package com.example;
// Declare any non-default types here with import statements
import com.example.Response;
interface IOizuuMain {
int app(String aString);
Response getResponseByString(String string);
}
but upon building the project, it gives me the following error in IappMain.java:
"error: incompatible types: Object cannot be converted to Response" at this line:
_result = com.example.Response.CREATOR.createFromParcel(_reply);
The error is being caused by this line:
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
Type parameters need to be added to both the return type and the object being created. The change to add type parameters is this:
public static final Parcelable.Creator<Response> CREATOR =
new Parcelable.Creator<Response>() {
try to add
public Response()
{}
above to the below mentioned code.
public Response(Parcel in) { .....
....
}
so it should look like
public Response(){}
public Response(Parcel in) { .....
....
}
Does Android AIDL support generics?
For example, assume that I have a class Result<T>, where T can be any type including primitives (via autoboxing) or other custom classes such as Car. Any custom classes implement Parcelable as required by Binder.
Then possible AIDL method signatures would be
Result<Car> m1();
Result<Void> m2();
Result<Boolean> m3();
From what I could gather, the AIDL compiler doesn't like things like Result<Animal> getResult();. However, Result getResult(); does work. So this is what I did:
Created a class with the signature public class Result<T extends Parcelable> implements Parcelable.
Created a new class to throw into the first one, which is called Animal. The signature is public class Animal implements Parcelable.
Had to implement methods required by interface Parcelable and a CREATOR in both Result and Animal, and also created one AIDL for each as is required and imported both classes in the main AIDL. This stuff is regular AIDL work and is describe in the AIDL site.
Inside Result, we store not only an object of type T but also a Class object. When writing the parcel we need to write first the class type and only then the generic object. When reading, we do it in the same order. We need to write the class type because when we read we have to do t = (T) in.readValue(classType.getClassLoader()); and without a class type we do not know which class loader to fetch. There are probably other ways to do this but this is how I've done it for this example.
When receiving on the client node, I can successfully do Result<Animal> r = MainActivity.this.service.getResult(); and then call methods on both Result and Animal.
Some code that will hopefully makes things more clearer can be found below.
public class Result<T extends Parcelable> implements Parcelable {
private String msg;
private Class classType;
private T value;
public Result(String msg, T value, Class classType) {
this.msg = msg;
this.value = value;
this.classType = classType;
}
// to reconstruct object
public Result(Parcel in) {
readFromParcel(in);
}
public String getMsg() {
return msg;
}
public T getValue() {
return value;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(msg);
dest.writeValue(classType);
dest.writeValue(value);
}
private void readFromParcel(Parcel in) {
this.msg = in.readString();
this.classType = (Class) in.readValue(Class.class.getClassLoader());
this.value = (T) in.readValue(classType.getClassLoader());
}
public static final Creator<Result> CREATOR = new Creator<Result>() {
#Override
public Result createFromParcel(Parcel source) {
return new Result(source);
}
#Override
public Result[] newArray(int size) {
return new Result[size];
}
};
}
public class Animal implements Parcelable {
private int n;
public Animal(int n) {
this.n = n;
}
public Animal(Parcel in) {
readFromParcel(in);
}
public int getN() {
return n;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(n);
}
private void readFromParcel(Parcel in) {
n = in.readInt();
}
public static final Creator<Animal> CREATOR = new Creator<Animal>() {
#Override
public Animal createFromParcel(Parcel source) {
return new Animal(source);
}
#Override
public Animal[] newArray(int size) {
return new Animal[size];
}
};
}
Excerpt from the Service:
#Override
public Result getResult() throws RemoteException {
Result<Animal> r = new Result<Animal>("this is an animal", new Animal(42), Animal.class);
return r;
}
Excerpt from the Client:
Result<Animal> r = MainActivity.this.service.getResult();
Log.d(TAG, "Received the following (Outer): " + r.getMsg());
Log.d(TAG, "Received the following (Inner): " + r.getValue().getN());
Another way to do it is changing the signature of Result into public class Result<T extends Serializable> implements Parcelable, making Animal implement Serializable, and then use dest.writeSerializable(value); and this.value = (T) in.readSerializable(); inside Result.
With this approach there is no need to send the class type to the other side or even use it at all. You will, nonetheless, pay the price.
Daniels solution almost worked for me except the thing with marshalling and unmarshaling classtype.
Instead of "dest.writeValue(classType);" and "this.classType = (Class) in.readValue(Class.class.getClassLoader());" I had to use "dest.writeSerializable(classType);" and "classType = (Class) in.readSerializable();" and it worked like a charm
Thank you Daniel
I am working on an Android App, and I am trying to pass information using Parcelable. So here's what I've got.
import android.os.Parcel;
import android.os.Parcelable;
abstract class Role implements Parcelable {
private String name;
private String image;
public Role() {
}
public Role(Parcel read) {
name = read.readString();
image = read.readString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String toString() {
return this.name;
}
public static final Parcelable.Creator<Role> CREATOR =
new Parcelable.Creator<Role>() {
public Role createFromParcel(Parcel source) {
return new Role(source);
}
public Role[] newArray(int size) {
return new Role[size];
}
};
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeString(name);
dest.writeString(image);
}
}
However, when I try to compile I get the Error (where I placed the comment)
Cannot instantiate the Type Role
Any thoughts on this?
Best regards
I have not used parcelable in abstract class myself, but it should be ok. You may want to check here or more generally here
I have a VERY similar class (two strings) but its a public static class.
I do new() on my string members in the constructor.
Yout class Role is defined as abstract, the abstract classes cannot be instantiated.
just define your class Role:
class Role implements Parcelable {
//...
}
As qjuanp mentioned, one cannot instantiate an abstract class (as per Java's and common OOP definition; you cannot instantiate something that is abstract, it has got to be more defined).
I'm sure you're trying to use some subclasses of Role (that's about the only way you can use both abstract and implement Parcelable here), consider using this approach:
public abstract class A implements Parcelable {
private int a;
protected A(int a) {
this.a = a;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(a);
}
protected A(Parcel in) {
a = in.readInt();
}
}
public class B extends A {
private int b;
public B(int a, int b) {
super(a);
this.b = b;
}
public static final Parcelable.Creator<B> CREATOR = new Parcelable.Creator<B>() {
public B createFromParcel(Parcel in) {
return new B(in);
}
public B[] newArray(int size) {
return new B[size];
}
};
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(b);
}
private B(Parcel in) {
super(in);
b = in.readInt();
}
}
I have seen many parcelable examples so far, but for some reason I can't get it to work when it gets a bit more complex.
I have a Movie object, which implements Parcelable. This book object contains some properties, such as ArrayLists.
Running my app results in a NullPointerException when executing the ReadTypedList ! I'm really out of ideas here
public class Movie implements Parcelable{
private int id;
private List<Review> reviews
private List<String> authors;
public Movie () {
reviews = new ArrayList<Review>();
authors = new ArrayList<String>();
}
public Movie (Parcel in) {
readFromParcel(in);
}
/* getters and setters excluded from code here */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeList(reviews);
dest.writeStringList(authors);
}
public static final Parcelable.Creator<Movie> CREATOR = new Parcelable.Creator<Movie>() {
public MoviecreateFromParcel(Parcel source) {
return new Movie(source);
}
public Movie[] newArray(int size) {
return new Movie[size];
}
};
/*
* Constructor calls read to create object
*/
private void readFromParcel(Parcel in) {
this.id = in.readInt();
in.readTypedList(reviews, Review.CREATOR); /* NULLPOINTER HERE */
in.readStringList(authors);
}
}
The Review class:
public class Review implements Parcelable {
private int id;
private String content;
public Review() {
}
public Review(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(content);
}
public static final Creator<Review> CREATOR = new Creator<Review>() {
public Review createFromParcel(Parcel source) {
return new Review(source);
}
public Review[] newArray(int size) {
return new Review[size];
}
};
private void readFromParcel(Parcel in) {
this.id = in.readInt();
this.content = in.readString();
}
}
I would be very grateful if someone could just get me on the right track, I have spend quite a bit of time searching for this one !
Thanks in adnvance
Wesley
reviews and authors are both null. You should first initialize the ArrayList. One way to do this is chain the constructor:
public Movie (Parcel in) {
this();
readFromParcel(in);
}
From the javadocs for readTypedList:
Read into the given List items containing a particular object type
that were written with writeTypedList(List)
at the current dataPosition(). The list must have previously been written via writeTypedList(List) with the same object type.
You wrote them with a plain
dest.writeList(reviews);