What is the correct way of implementing the Parcelable interface in Android? According to the documentation you should implement the writeToParcel method and have a CREATOR.
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
But when I implement it without adding a CREATOR and leaving the writeToParcel() empty the app still seems to work correctly. Sometimes I would get a Bad Parcelable Exception but I can't work out the steps to replicate.
This is how I use to pass an object from activity to fragment
Bundle bundle = new Bundle();
bundle.putParcelable(PageFragment.PAGE_FILTER_KEY, page);
fragment.setArguments(bundle);
So, what is the purpose of adding stuff like out.writeInt(mData); what kind of problems can be expected if this is not done?
Parcelable implementation mainly have two process steps.
1 Writing your java object to Parcel which includes two methods.
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(cityName);
dest.writeString(macroName);
dest.writeString(id);
}
where describe content is for setting a flag for your contents. Most of time you just need to it untouched.
public void writeToParcel(Parcel dest, int flags) , you need to write you Java class object to parcel step by step according to fields in JAVA class. In above example my class has three strings. You can write almost all types of objects in parcel. You just need to chose appropriate one. Like writeString(),writeList() or writeObject() etc.
2. Second part is reading your java object back from parcel
This part required two things as well. First is CREATOR of your java class like following
public static final Creator<City> CREATOR = new Creator<City>() {
#Override
public City createFromParcel(Parcel in) {
return new City(in);
}
#Override
public City[] newArray(int size) {
return new City[size];
}
};
In above example my Java class is City. It makes read a City object from parcel. But it calls new City(in) constructor of City class. So now I need a constructor which accept a parcel object in arguments. Lets create that too..
protected City(Parcel in) {
cityName = in.readString();
macroName = in.readString();
id = in.readString();
}
Now we make a class complete full proof parcelable. One thing to notice, we need to read members in same sequence at protected City(Parcel in) we put them in parcel i.e. in writeToParcel() method.
On how to reproduce badParcelable exeption in simply letting android create java object from parcelable. For that you can choose Destroy activities from developer options on android device and put you app in background in that activity, so android kill your application process ID. Resume your app by recreating activity (onCreate + Bundle), you will get that exception if you does not implemented parcelable correctly.
But when I implement it without adding a CREATOR and leaving the writeToParcel() empty the app still seems to work correctly.
CREATOR is used when reading data back out of a Parcel and converting it back into objects.
writeToParcel() puts your data into the Parcel.
The only way that leaving those off will work correctly is in cases where your Parcelable is not actually being put into a Parcel or reconstituted from a Parcel. Examples include LocalBroadcastManager.
what is the purpose of adding stuff like out.writeInt(mData);
It would be the same purpose as adding stuff like out.write() with an OutputStream: it writes to the output. Your question is akin to asking "hey, if I don't write data to my file, what sorts of problems will I encounter?".
Related
I'm trying to pass custom object between two activities, the custom object has array list of objects and both of them are implementing parcelable interface. When trying to read array list of objects the object from index 1 onwards is null and am getting an error as Class Not Found Exception.
Note: am using createTypedArrayList and writeTypedArrayList respectively to read and write from parcel any help is appreciated.
Try this:
public void writeToParcel(Parcel out, int flags) {
out.writeTypedList(_devices);
}
private SomeClass(Parcel in) {
in.readTypedList(_devices, SomeClass.CREATOR);
}
all code details you may found here.
P.S. You can write/read only primitive type with Parcelable. Do not look at the language, just read the code :)
I'm asking this question: instread of giving a string, a int and so on, can we push a custom object during the creation fo a new Intent?
newActivity.PutExtra("JsonDataResult", business.getJSON());
In fact I have one object constructed thanks to a JSON (from webrequest) , I parse it and I put it on an object.
At this point I'm passing the string returned from the webrequest to another intent but the parsing takes a long time tu be done, so it could be super-cool the ability to pass custom object with intent.
EDIT : I'm using monodroid / xamarin, so
Android.OS.IParcelable cannot be implemented,
Java.IO.ISerializable cannot be implemented.
You can either let your custom classes implement Parcelable (Google says its faster, but you have to do more coding) or Serializable.
Then add your objects to a bundle (or to the "extra"):
Bundle b = new Bundle()
b.putParcelable("myObject",myObject);
b.putSerializable("myObject",myObject);
For info to Parcelablecheckout this
And if you're interested in the difference between Parcelable and Serializable in more detail check out this
I personally prefer the usage of Serializable for simple object-passing, since the code ist not spoiled with so much code.
Edit: ok isn't your question very similar to this then?
As you've specified you're using Monodroid, it looks like it's not straightforward. I did a quick search and found this forum post
Which listed the following solutions to this problem in Monodroid:
Store the custom Object to be passed as a global variable somewhere, and just read it from your second activity
Which is a bit messy and bad practice, but would work.
Or
serialize your class to a string and send the string to the second Activity
Which will be a little more hard work, but better practice
This is an example how to create a Parcelable class:
public class Person implements Parcelable {
private String name;
private String surname;
private String email;
// Get and Set methods
#Override
public int describeContents() {
return hashCode();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(surname);
dest.writeString(email);
}
// We reconstruct the object reading from the Parcel data
public Person(Parcel p) {
name = p.readString();
surname = p.readString();
email = p.readString();
}
public Person() {}
// We need to add a Creator
public static final Parcelable.Creator<person> CREATOR = new Parcelable.Creator<person>() {
#Override
public Person createFromParcel(Parcel parcel) {
return new Person(parcel);
}
#Override
public Person[] newArray(int size) {
return new Person[size];
}
};
Give a look here if you want to use Parcelable.
This has been asked a few times here on SO, but my case is a bit different.
I have class A that implements Parcelable. Class A contains some member data that can be parceled. It has its own CREATOR and implements writeToParcel(), describeContents(), and a constructor that accepts a Parcel.
There is class B that extends from class A. Class B has additional member data, but none of them need to be parceled. Basically, class B's parcelable data is the same as class A. If I try to put B in a Bundle, pass it to another Activity, and read it back, I would get a ClassCastException. I guess that's expected.
After a bit of trial-and-error, in order to make class B parcelable, I have to implement at least these two things:
public static final Parcelable.Creator<B> CREATOR
= new Parcelable.Creator<B>() {
public B createFromParcel(Parcel source) {
return new B(source);
}
public B[] newArray(int size) {
return new B[size];
}
};
public B(Parcel in) throws JSONException {
super(in);
}
So my concern is this. There are about half a dozen or more classes that extend from A and all have the same issue as B. It seems silly that each one of them has to add their own static CREATOR and a constructor that accepts a Parcel, only to pass it back to A. Everything else is identical. The only thing that makes it different is the name of the class. It beats the purpose of having inheritance in the first place.
For example, if there's another class C that extends B, I need to do the same:
public static final Parcelable.Creator<C> CREATOR
= new Parcelable.Creator<C>() {
public C createFromParcel(Parcel source) {
return new C(source);
}
public C[] newArray(int size) {
return new C[size];
}
};
public C(Parcel in) throws JSONException {
super(in);
}
Is there some sort of clever techniques in Java to automate this process? Perhaps using generic of some sort? If there's no other way, I might just as well just remove the inheritance lineage, and require each class to implement Parcelable themselves.
This is a little complicated, but the only way I can think of offhand involves reflection - Provided all of the subclasses have a constructor that takes a Parcel that then calls super(parcel), you could make the class name a part of the parcel - then in your createFromParcel method of A:
public A createFromParcel(Parcel source) {
Class clazz = Class.forName(source.readString());
Class[1] paramTypes = { Parcel.class };
Constructor ctor = clazz.getConstructor(paramTypes);
A myA = (A) ctor.newInstance(source);
return myA;
}
Note that this was written largely off the cuff and may need some tweaking before it runs (I know for sure it's missing checked exception handlers) - but hopefully the idea is clear
I have implemented the solution described by JRaymond and it works. It's a little complicated since it involves some reflection,but i hope it helps someone else. Now any class that is parcelable should extend this GlobalParcelable class which implements parcelable.
https://github.com/awadalaa/Android-Global-Parcelable
In my code I created a card class; I show a bunch of cards later in a gridview.
On screen orientation change I am loosing all the cards; with my previous question I was pointed in the right direction.
Now, what I have found in Android documentation and here in StackO is that
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(...);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
some_int_var = savedInstanceState.getInt(...);
super.onRestoreInstanceState(savedInstanceState);
}
Now, I do that OK, since the Bundle object has several methods like putString, Char, etc. The primitives plus string that is. But what about my cards? they are Card objects in a vector thus I can't use any of those methods.
How can I restore that vector WITHOUT using onRetainNonConfigurationInstance nor preventing the activity reset? In Android documentation it is advice to do this if there's heavy data to restart but that's not my case.
For your own objects you can use putParcelable()
To make your object parcelable you should implement Parcelable and follow the following example to implement it.
http://prasanta-paul.blogspot.nl/2010/06/android-parcelable-example.html
So:
public class ParcelData implements Parcelable {
String name;
ParcelData (Parcel source) {
name = source.readString();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
public class MyCreator implements Parcelable.Creator<ParcelData> {
public ParcelData createFromParcel(Parcel source) {
return new ParcelData(source);
}
public ParcelData[] newArray(int size) {
return new ParcelData[size];
}
}
}
first of all - you can pass with intent complex objects.
the why to do that is to make your class implement Serializable interface, or Parcelable,
and then use the intent.getSerializableExtra(keyName); or intent.getParcelableExtra();
Serializable is the easier to implement (basically declaring your class as implementing it is enough) but some specific classes cannot be serialized, while implementing Parcelable require a bit more work, but always possible.
other option - is holding the data you need to save in some singeltone class with getters and setters for save what ever you'd like to save, and use it anytime and anywhere in your code:
saving your data in the onSaveInstanceState method, and getting it back in onRestoreInstanceState method.
I am developing simple travel agent android application. When application starts it loads list of cities, in the next activity user will see source and destination spinners, once user selects proper source and destination, it will take to third activity, it displays available travels if user selects one it takes to fourth activity in which user selects seat and continues to book ticket. In order to maintain complete user session I am maintaining a UserSession class, which is as follows (removing unnecessary logic in the code which is irrelevant to problems)
public class UserSession implements Parcelable {
List<City> citiesList;
HashMap<String, City> cityMap; // Map city name to code
RouteSearchResult searchedRoutes;
String sourceCity;
String destinationCity;
LocalStop selectedBoardingPoint;
LocalStop selectedArrivalPoint;
#Override
public void writeToParcel(Parcel parcel, int flags) {
try {
parcel.writeList(citiesList);
parcel.writeMap(cityMap);
parcel.writeValue(searchedRoutes);
parcel.writeString(sourceCity);
parcel.writeString(destinationCity);
parcel.writeValue(selectedBoardingPoint);
parcel.writeValue(selectedArrivalPoint);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class City implements Parcelable {
String cityName;
String cityId;
#Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(cityName);
parcel.writeString(cityId);
}
}
public class RouteSearchResult {
City source;
City destination;
String date;
List<RouteDetails> routeDetails;
}
I have the following problems
1) I am getting run time exception while writing
W/System.err( 817): java.lang.RuntimeException: Parcel: unable to marshal value com.travelagent.City#40605878
W/System.err( 817): at android.os.Parcel.writeValue(Parcel.java:1132)
W/System.err( 817): at android.os.Parcel.writeList(Parcel.java:519)
W/System.err( 817): at com.travelagent.UserSession.writeToParcel(UserSession.java:201)
W/System.err( 817): at android.os.Parcel.writeParcelable(Parcel.java:1151)
2) I commented parcel.writeList(citiesList); statement to see if I get any more problems, I got similar problem with parcel.writeValue(searchedRoutes);
I have following questions,
1) How to make a list of custom objects as parcelable?
2) Is it mandatory to make custom class like City also parcelable, which is part of actual class to be parceled.
3) Is there any approach to solve this kind of problems like passing user session to all activities.
Please help, I searched, but I couldn't find appropriate solution to make custom class parcelable.
My understanding is that you shouldn't use Parcels for this. From the android documentation on Parcels:
Parcel is not a general-purpose serialization mechanism. This class (and the corresponding Parcelable API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport. As such, it is not appropriate to place any Parcel data in to persistent storage: changes in the underlying implementation of any of the data in the Parcel can render older data unreadable.
What you should do is create two static methods for each class you want to transfer to the next activity. Call one of them toBundle() and one of them fromBundle(). The first converts a given instance of you class to a bundle while the second creates a new instance of your class from a bundle. Every time you need to pass an object on to the next Activity, just bundle it up using the toBundle() method and add it to the Intent you're using to start the next Activity. In the next Activity, you can recreate the object by calling fromBundle().