I haven't understood how to create the code needed to implement correctly the Parcelable for an object that contains GregorianCalendar objects.
E.g. for an object User that contains String name; and GregorianCalendar creationDate;, my attempt is this:
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeParcelable(this.creationDate, flags);
}
private User(Parcel in) {
this.name = in.readString();
this.creationDate = in.readParcelable(GregorianCalendar.class.getClassLoader());
}
public static final Creator<User> CREATOR = new Creator<User>() {
public User createFromParcel(Parcel source) {
return new User(source);
}
public User[] newArray(int size) {
return new User[size];
}
};
that unfortunately doesn't work
in writeToParcel() at the line
dest.writeParcelable(this.creationDate, flags);
get writeParcelable cannot be applied to GregorianCalendar error
in
this.creationDate = in.readParcelable(GregorianCalendar.class.getClassLoader());
get Incompatible types error
How to code correctly the Parcelable?
EDIT
I have tried some code generators but use different ways and I'm not sure what is the right implementation, the first one use writeValue and readValue in this way:
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeValue(creationDate);
}
protected User(Parcel in) {
name = in.readString();
creationDate = (GregorianCalendar) in.readValue(GregorianCalendar.class.getClassLoader());
}
the second one use
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeSerializable(creationDate);
}
protected User(Parcel in) {
name = in.readString();
creationDate = (GregorianCalendar) in.readSerializable();
}
What is the right way?
You could use the second way since GregorianCalendar implements Serializable, and the Parcelable will work.
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeSerializable(creationDate);
}
protected User(Parcel in) {
name = in.readString();
creationDate = (GregorianCalendar) in.readSerializable();
}
However you MUST NOT serialize GregorianCalendar, because updates in GregorianCalendar related class can cause issue. Consider the case you load a file that contains GregorianCalendar objects created with a different API version, the difference in GregorianCalendar implementation will lead to sure errors, is enough a little difference in serialVersionUID costant to prevent the correct load of the file.
Use long parameters to store the millisecond of the date, if you don't want to rewrite your whole application you can easily create a couple of methods to convert from millis to GregorianCalendar and vice-versa as you need.
Related
I have problems with reaching parcelable between activities. When I inspect the object before sending it to another activity it looks fine, but after receiving it I can only see a strange id and the rest of the fields is nulled. Why does it happen? Maybe the way I do it is wrong? It would be best, you will take a look at the code parts:
This is the parcelable object:
#Data
#NoArgsConstructor
public class Scene implements Parcelable
{
private Long id;
private String name;
private Light light;
private Track effect;
private Track music;
private Track ambience;
protected Scene(Parcel in)
{
if (in.readByte() == 0) { id = null; } else { id = in.readLong(); }
name = in.readString();
}
public static final Creator<Scene> CREATOR = new Creator<Scene>()
{
#Override
public Scene createFromParcel(Parcel in)
{
return new Scene(in);
}
#Override
public Scene[] newArray(int size)
{
return new Scene[size];
}
};
#Override
public int describeContents()
{
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags)
{
Map<String, Track> audio = new HashMap<>();
audio.put(Tags.EFFECT.value(), effect);
audio.put(Tags.MUSIC.value(), music);
audio.put(Tags.AMBIENCE.value(), ambience);
dest.writeLong(id);
dest.writeString(name);
dest.writeMap(audio);
}
}
The scene has a map containing audio files. They are serializable (or should it be better also parcelable?) Nothing complicated, simple POJO:
#Data
public class Track implements Serializable
{
private Long id;
private String name;
private String path;
private Long duration;
private String artist;
private String tag;
}
And finally how I get the object:
public class AudioPlayer extends Service
[...]
public int onStartCommand(Intent intent, int flags, int startId)
{
Scene scene = intent.getParcelableExtra("scene");
[...]
}
}
When I inspect the object by intent.setParcelableExtra(), I get this:
Scene(id=7, name=test, light=null, effect=Track(id=10, name=file.mp3, path=/some/path/file.mp3, duration=13662, artist=null, tag=effect), music=Track(id=11, name=other_file.mp3, path=/some/other/path/other_file.mp3, duration=189492, artist=null, tag=music), ambience=Track(id=12, name=another_other_file.mp3, path=/some/another/other/path/another_other_file.mp3, duration=10109, artist=null, tag=ambience))
When I inspect the object by intent.getParcelableExtra(), I get this:
Bundle[{scene=Scene(id=17179869184, name=null, light=null, effect=null, music=null, ambience=null)}]
I tried also make the Object Track parcelable but the effect was the same.
Please help me solve this issue. I donĀ“t understeand what goes wrong.
When implementing Parcelable, you must guarantee that everything you "write" is exactly matched by a corresponding "read" call. Let's look at your code:
#Override
public void writeToParcel(Parcel dest, int flags)
{
Map<String, Track> audio = new HashMap<>();
audio.put(Tags.EFFECT.value(), effect);
audio.put(Tags.MUSIC.value(), music);
audio.put(Tags.AMBIENCE.value(), ambience);
dest.writeLong(id);
dest.writeString(name);
dest.writeMap(audio);
}
protected Scene(Parcel in)
{
if (in.readByte() == 0) { id = null; } else { id = in.readLong(); }
name = in.readString();
}
Here you are writing a long, then a string, and then a map. But you are reading a byte, then conditionally a long, and then a string. You never read the map back out, and you also never write your light value.
These issues will need to be addressed. Here's what that might look like:
#Override
public void writeToParcel(Parcel dest, int flags)
{
if (id != null) {
dest.writeInt(1);
dest.writeLong(id);
} else {
dest.writeInt(0);
}
dest.writeString(name);
dest.writeSerializable(light);
dest.writeSerializable(effect);
dest.writeSerializable(music);
dest.writeSerializable(ambience);
}
protected Scene(Parcel in)
{
if (in.readInt() == 1) {
this.id = in.readLong();
} else {
this.id = null;
}
this.name = in.readString();
this.light = (Light) in.readSerializable();
this.effect = (Track) in.readSerializable();
this.music = (Track) in.readSerializable();
this.ambience = (Track) in.readSerializable();
}
I'm using the class Tests as the base class, the I created three more classes, Test1, Test2, Test3, they extends Tests class, then I have one more class, States which has an Arraylist.
States is used to gather a bunch of info including a list with the tests I want to perform, so I use the Arraylist and the method "add" to add test1, test2, or test3 to the list, then I want to send this State object to the activity B. I've implemented the parcelable interface on classes Test1, Test2, Test3 and States but I'm getting the next exception:
Unmarshalling unknown type code 6357090 at offset 300
Please, can suggest any way to achieve this, It's important to gather the tests on the arraylist, i think there lies the problem, thanks.
Sorry, this is too long for a comment, so I posted as an answer
Since Test1 extends Tests, Tests should haveit's own Parcelable implementation.
This implementation is the called by all its 'child' classes by using super. For example (this is what I use in my apps):
Tests class
public class Tests implements Parcelable {
private int Id;
private String Name;
// parcelable
protected Tests(Parcel in) {
Id = in.readInt();
Name = in.readString();
}
public static final Creator<Tests> CREATOR = new Creator<Tests>() {
#Override
public Tests createFromParcel(Parcel in) {
return new Tests(in);
}
#Override
public Tests[] newArray(int size) {
return new Tests[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(Id);
dest.writeString(Name);
}
}
Test1 class
public class Test1 extends Tests implements Parcelable {
private int Score;
// parcelable
protected Test1(Parcel in) {
super(in);
Score = in.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(Score);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<Test1> CREATOR = new Creator<Test1>() {
#Override
public Test1 createFromParcel(Parcel in) {
return new Test1(in);
}
#Override
public Test1[] newArray(int size) {
return new Test1[size];
}
};
}
I have been searching for a way to pass an object from one Activity to another.
Different tutorials stated that the best way to do it is to make the class Parcelable. I've managed to implement it, but I have one question left.
There is a reference to another parcelable object (location) inside the Office class. This tutorial tells to serialize it using dest.writeParcelable(location, flags); and in.readParcelable(LatLng.class.getClassLoader());, but the parcelabler created the code with dest.writeValue(location); and then (LatLng) in.readValue(LatLng.class.getClassLoader());.
I have checked and it worked both ways.
Could somebody please explain what is the difference between these two approaches? Is any of them better for some reasons? Thank you!
public class Office implements Parcelable {
#SuppressWarnings("unused")
public static final Parcelable.Creator<Office> CREATOR = new Parcelable.Creator<Office>() {
#Override
public Office createFromParcel(Parcel in) {
return new Office(in);
}
#Override
public Office[] newArray(int size) {
return new Office[size];
}
};
public final String name;
public final String address;
public final LatLng location;
public Office(String name, String address, LatLng location) {
this.name = name;
this.address = address;
this.location = location;
}
protected Office(Parcel in) {
name = in.readString();
address = in.readString();
// location = (LatLng) in.readValue(LatLng.class.getClassLoader());
location = in.readParcelable(LatLng.class.getClassLoader());
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(address);
// dest.writeValue(location);
dest.writeParcelable(location, flags);
}
}
writeValue is more generic, and since it takes an Object as parameter, internally they check the instanceOf the object to call the specific method. If you know the type, I would stick with using the specific one
My Parcelable Class :
public class Category implements Parcelable{
int id;
String name;
Department department;
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeParcelable(department, 10);
}
public static final Parcelable.Creator<Category> CREATOR = new Parcelable.Creator<Category>() {
public Category createFromParcel(Parcel in) {
return new Category(in);
}
public Category[] newArray(int size) {
return new Category[size];
}
};
public Category(Parcel in){
id=in.readInt();
name=in.readString();
department= in.readParcelable(department.getClass().getClassLoader());
}
When i send object of category class to other activity, then writeToparcel() and createFromParcel() method got called, I observed NullPointerException at
department= in.readParcelable(department.getClass().getClassLoader());
But while debugging i had checked that in writeToParel() method department object is stored correct, but how that is not returned back, in createFromParcel() , Same code is running fine in Android Studio 1.1 .
Currently I had changed my implementation code like that :
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeParcelable(department, Constants.PARCELABLE_DEPARTMENT);
dest.writeInt(department.getId());
dest.writeString(department.getName());
}
public Category(Parcel in){
id=in.readInt();
name=in.readString();
department=new Department(in.readInt(),in.readString());
}
Now, the code is working fine , but now i need to create extra department object, which i think is not good way .
Does any one have any solution regarding the problem ?
department.getClass().getClassLoader()
This is what throws the error. department == null and you're trying to fetch it's class. Therefore it throws an NPE.
Instead, fetch the class loader via the class object:
Department.class.getClassLoader()
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.