I'm trying to come to grips with the onSaveInstanceState method in class View (not the one in class Activity). That method does return a Parcelable. I derived my own View from ViewGroup and overrode that method to save my own state. But when the state was to be saved I got an exception:
java.lang.IllegalStateException:
Derived class did not call super.onSaveInstanceState()
That is true enough, but simply calling that method doesn't seem enough to me. So how should I do this? If the method would get passed a Parcel to write to, I could simply pass that same parcel to the super class, so things would get written sequentially. But this is a return value.
Should I include this returned object as a member of my own Parcelable representation, and use Parcel.writeParcelable to marshal it along with my own data if needed? Or is there some better way to handle parent invocation and chaining of parcelable objects? If so, what class loader should I use when loading the instance state of the super class?
Since zapl didn't turn his comment into an answer, I'm doing so.
is there some better way to handle parent invocation and chaining of parcelable objects?
The canonical way to accomplish this is by having your own class for saved data derived from View.BaseSavedState, which in turn is derived from AbsSavedState. You can call the onSaveInstance handler of the parent class and pass the resulting object to the constructor of your own class. When restoring the data, getSuperState gives the instance aimed at the parent class.
A typical code example could look like this:
static class SavedState extends View.BaseSavedState {
// include own data members here
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
// read own data here
}
#Override public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
// write own data here
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) { return SavedState(in); }
public SavedState[] newArray(int size) { return new SavedState[size]; }
};
}
#Override public Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
// set data members here
return state;
}
#Override public void onRestoreInstanceState(Parcelable parcelable) {
SavedState state = (SavedState)parcelable;
super.onRestoreInstanceState(state.getSuperState());
// restore from data members here
}
The above was adapted from this presentation by Cyril Mottier, but should also be a close match to how designers intended the use of this class in general.
Should I include this returned object as a member of my own Parcelable representation, and use Parcel.writeParcelable to marshal it along with my own data if needed?
Although the mentioned described above seems to be preferred, behind the scenes it does rely on writeParcelable as well. So if there are reasons to not use that base class, simply calling writeParcelable to store the state of the super class should be fine.
what class loader should I use when loading the instance state of the super class?
The current implementation of AbsSavedState does use null as the class loader argument, causing the use of the default class loader. However, that line of code is marked with a FIXME comment, so it might change one day.
Related
I happen to know what if my class Info which implements Parcelable has empty writeToParcel() method, Bundle.putParcelableArrayList() and Bundle.getParcelableArrayList() methods still work on ArrayList< Info>.
public class Info implements Parcelable
{
public int row;
public int column;
public int describeContents()
{
return 0;
}
public void writeToParcel(Parcel out, int flags)
{
// Left empty!
}
}
So the question is why? When I really should implement writeToParcel method as API documentation and books instruct?
The Android Bundle class does not follow the same protocol that is followed during IPC marshaling. Through reflection, a class that implements Bundle will simply read & write the Parcelable object into its own internal mapping. So, whatever properties you define in your Parcelable derived class will be used.
On the contrary, if you're using the IPC Marshalling protocol - e.g. passing your Info object from one activity to another - this is when you need to implement a specific writeToParcelable method and the appropriate constructor.
I have a CustomAddress class that extends the android.location.Address class that implements Parcelable.
I am trying to make my CustomAddressimplement Parcelableto but am stuck when creating my class from a parcel. What I want to when creating CustomAddressfrom a parcel is first fill in all the fields from the super class Addressand then my own fields. So I have implemented the CREATORfield:
public static final Parcelable.Creator<CustomAddress> CREATOR
= new Parcelable.Creator<CustomAddress>() {
public CustomAddress createFromParcel(Parcel in) {
return new CustomAddress(in);
}
public CustomAddress[] newArray(int size) {
return new CustomAddress[size];
}
};
But in my CustomAddress(Parcel in)creator, I can't call super(in)because it doesn't exist in android.location.Address. I can only access android.location.Address.CREATOR. So how do I fill in my fields using CREATOR?
EDIT: link to the android Address class https://developer.android.com/reference/android/location/Address.html
Here is a similar question and Mark Murphy's excellent answer: https://stackoverflow.com/a/10841502/1140682
So, in your case, you would make CustomAddress extend Address (as you already do), call the super() method in the constructor and then read your own attributes from the passed Parcel. Same has to be done (in same order) in the writeToParcel() method, of course here adding your attributes to the parcel.
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.
Let me first give you the whole picture: I am developing a location based application, that invokes a constant recreation and exchange of objects, among several activities and services. The necessary data to create the objects being exchanged, is stored in an SQLite database, that in its turn gets populated by retrieving data from a remote SQL database.
It got quickly obvious, that passing raw attributes of an object to activities/services (via intents), was a huge coding overhead, diminishing every extension prospects the application might have. So, soon i decided to extend my main object class to implement a Parcelable one, as shown below:
public class MyProduct implements Parcelable {
//MyProduct Attributes
private int myProductId;
private String myProductDescription;
private float myProductRadius;
//More attributes...
public MyProduct() {
myProductId=-1;
myProductDescription="defaultProductDescription";
myProductRadius=10;
//More attributes
}
public int describeContents(){
return 0;
}
// write your object's data to the passed-in Parcel
public void writeToParcel(Parcel out, int flags){
//Product attributes
try{
out.writeInt(myProductId);
out.writeString(myProductDescription);
out.writeFloat(myProductRadius);
//More attributes
}
catch (Exception e){}
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<MyProduct> CREATOR = new Parcelable.Creator<MyProduct>() {
//public class MyCreator implements Parcelable.Creator<MyProduct> {
public MyProduct createFromParcel(Parcel in) {
return new MyProduct(in);
}
public MyProduct[] newArray(int size) {
return new MyProduct[size];
}
};
// example constructor that takes a Parcel and gives you an object populated with it's values
private MyProduct(Parcel in) {
//in.readParcelable(MyProduct.class.getClassLoader());
try{
//MyProduct.class.getClassLoader();
myProductId=in.readInt();
myProductDescription=in.readString();
myProductRadius=in.readFloat();
//More attributes
}
catch(Exception e){}
}
//Setters and Getters
}//endOfMyProduct.class
Although i checked every data entry to the parcel fields, the following exception keeps spawning:
01-05 19:35:11.570: ERROR/Parcel(59): Class not found when unmarshalling: com.nifo.distribution.MyProduct, e: java.lang.ClassNotFoundException: com.nifo.distribution.MyProduct
For this reason, i consider of the MyProduct.class implementing serializable, hoping that it will turn to be a more error-forgiving structure. What would be the pros and cons of such alternation in the case described above?