Restoring view SavedState throws ClassNotFoundException when unmarshalling parent view SavedState - android

I have 2 classes ParentClass and ChildClass:
ParentClass is a subclass of RelativeLayout which saves state using onSaveInstanceState() / onRestoreInstanceState() with following code:
#Override
protected Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState(super.onSaveInstanceState());
savedState.someObj = someObj;
savedState.someInt = someInt;
savedState.someEnum = someEnum;
return savedState;
}
#Override
protected void onRestoreInstanceState(Parcelable in) {
if (!(in instanceof SavedState)) {
super.onRestoreInstanceState(in);
return;
}
SavedState savedState = (SavedState) in;
super.onRestoreInstanceState(savedState.getSuperState());
this.someObj = savedState.someObj;
this.someInt = savedState.someInt;
this.someEnum = savedState.someEnum;
}
And here is the SavedState code:
public static class SavedState extends BaseSavedState {
private SomeObj someObj;
private int someInt;
private SomeEnum someEnum;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
someInt = source.readInt();
someEnum = SomeEnum.values()[source.readInt()];
someObj = source.readParcelable(SomeObj.class.getClassLoader());
}
#Override
public void writeToParcel(#NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(someInt);
dest.writeInt(someEnum.ordinal());
dest.writeParcelable(someObj, flags);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
Nothing fancy, everything works as expected (saving \ restoring state) when I use this view directly.
Then I have ChildClass which extends ParentClass It also saves it state using exactly same code, except that I am saving only long and int fields.
When the app tries to restore ChildClass view state by calling it SavedState constructor with Parcel from CREATOR it crashes with following stack:
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.company.app.ParentClass$SavedState
1 android.os.Parcel.readParcelableCreator Parcel.java, line 2320
2 android.os.Parcel.readParcelable Parcel.java, line 2270
3 android.view.AbsSavedState.<init> AbsSavedState.java, line 57
4 android.view.View$BaseSavedState.<init> View.java, line 20128
5 com.company.app.ChildClass$SavedState.<init> ChildClass.java, line 151
6 com.company.app.ChildClass$SavedState$1.a ChildClass.java, line 166
7 com.company.app.ChildClass$SavedState$1.createFromParcel ChildClass.java, line 164

Make your SavedState class implement Parcelable
public static class SavedState extends Parcelable {

Related

Extending MaterialButton: Should it be done? SavedState has package visibility

I have extended the MaterialButton to display a consistent loading state on the button when clicked however I ran into a minor issue when implementing SavedState restoration.
The progress button variant of this I have written, disables the view, displays an animated loading spinner, optionally displays loading text and restores then restores the previously displayed text/state when the view is enabled.
MaterialButton.SavedState is package-private so cannot be extended externally. This does not create a problem with my implementation as I don't use the 'checked' field it extends but it does raise these questions:
TLDR
Is it wrong to extend this MaterialButton?
If not would it be appropriate to submit a PR to the public repo to make SavedState public?
material-components-android:1.2.0
Is it wrong to extend this MaterialButton?
The MaterialButton is not a final class then you can extend it, and for example the ExtendedFloatingActionButton extends it.
If not would it be appropriate to submit a PR to the public repo to make SavedState public?
You don't need to make MaterialButton.SavedState public. You can do something like:
public class MyButton extends MaterialButton
{
private String text;
//....
static class SavedState extends AbsSavedState {
#Nullable CharSequence myText;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(#NonNull Parcel source, ClassLoader loader) {
super(source, loader);
myText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
}
#Override
public void writeToParcel(#NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
TextUtils.writeToParcel(myText, dest, flags);
}
#NonNull
#Override
public String toString() {
return "MyButton.SavedState{"
+ " text="
+ myText
+ "}";
}
public static final Creator<SavedState> CREATOR =
new ClassLoaderCreator<SavedState>() {
#NonNull
#Override
public SavedState createFromParcel(#NonNull Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Nullable
#Override
public SavedState createFromParcel(#NonNull Parcel in) {
return new SavedState(in, null);
}
#NonNull
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
#Nullable
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.myText = text;
return ss;
}
#Override
public void onRestoreInstanceState(#Nullable Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
text = ss.myText.toString();
}
}
In this way the MyButton.SavedState will contain also the MaterialButton.SaveState without extending it.

Android FragmentStatePagerAdapter for custom View

because I don't like the default implementation of Android's Bottom Navigation Bar, I have made my own and connected it with a FragmentStatePagerAdapter. I just want to know how I can get already created Fragments with their saved instance state from my adapter just like viewpager does?
It would be nice if anyone could help me solve this.
I solved it on my own. I totally had forgotten to care about the Saved Instance State of my view. I got it from the ViewPager source code and modified it slightly because some Methods were marked as deprecated. Here the implementation.
#Nullable
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SaveState ss = new SaveState(superState);
ss.position = lastSelectedPosition;
ss.itemId = lastSelectedItemId;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SaveState)) {
super.onRestoreInstanceState(state);
return;
}
SaveState ss = (SaveState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setSelectedItem(ss.position, ss.itemId);
lastSelectedItemId = ss.itemId;
lastSelectedPosition = ss.position;
}
}
public static class SaveState extends BaseSavedState {
int position;
int itemId;
Parcelable adapterState;
ClassLoader loader;
SaveState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(position);
out.writeInt(itemId);
out.writeParcelable(adapterState, flags);
}
public static final Parcelable.ClassLoaderCreator<SaveState> CREATOR = new
Class later dater<SaveState>() {
#Override
public SaveState createFromParcel(Parcel source) {
return new SaveState(source, null);
}
#Override
public SaveState[] newArray(int size) {
return new SaveState[size];
}
#Override
public SaveState createFromParcel(Parcel source, ClassLoader loader) {
return new SaveState(source, loader);
}
};
SaveState(Parcel in, ClassLoader loader) {
super(in);
if (loader == null) {
loader = getClass().getClassLoader();
}
position = in.readInt();
itemId = in.readInt();
adapterState = in.readParcelable(loader);
this.loader = loader;
}
}

Parcelable and boxed classes

I've got the following class:
public class E implements Parcelable {
#SerializedName("a")
private String a= null;
#SerializedName("b")
private BigDecimal b= null;
#SerializedName("c")
private String c= null;
#SerializedName("d")
private String d= null;
protected E(Parcel in) {
number = in.readString();
expirationYear = in.readString();
expirationMonth = in.readString();
}
public static final Creator<E> CREATOR = new Creator<E>() {
#Override
public E createFromParcel(Parcel in) {
return new E(in);
}
#Override
public E[] newArray(int size) {
return new E[size];
}};
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(number);
dest.writeString(expirationYear);
dest.writeString(expirationMonth);
}
}
How to parcel the data member b? Or better yet, how to parcel boxed types?
As you can see, the writeToParcel method misses the data member b as well as the protected c'tor.
See:
https://medium.com/the-wtf-files/the-mysterious-case-of-the-bundle-and-the-map-7b15279a794e#.sjbki9dss
How to use Parcelable on non-primitive types?
A good way to solve this problem (in my opinion) is to create your class extending BigDecimal and implements Parcelable interface on it.

use of Parcelable interface..?

explain what meaning of Parcelable.creator?
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];
}
};
Parcelable objects allow you to serialise and deserialise on activity or fragment stop / start and it is Faster than Java serialisable.
The static CREATOR class creates your object from a Parcel via the createFromParcel method that takes in a parcel and passes it to a constructor in your class that does the grunt work.
The newArray method allows an array of your objects to be parcelled. Answer
And here is a good example of it Link
Parcelable.Creator creates instances of your MyParcelable from a Parcel object. For example the typic Parcelable should look like this :
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();
}
}
For more information you should check the official documentation :
Parcelable.Creator
Parcelable

how to pass objects between intents

I have a class that contains data i want to pass it between intents, this class have arraylist that contains another class objects. this is my class
public class ParsedData implements Parcelable {
public String error;
public float protectionLevel;
public int protectionLevelColor;
public double lastBackup;
public boolean compressedType;
public Long driveFreeSpaceSize;
ArrayList<Item>Items = new ArrayList<Item>();
}
class Item {
public String name;
public float percentage;
public int files;
public long size;
}
how can i send this class between intents ?
you can let your class Item implements the Serializable interface, and use Intent.putExtra(String, Serializable). Since ArrayList implements also the Serializable interface, you can pass the whole Items object.
This may be your problem:
Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface.
Alternatively, I'd try to have Item implement Parcelable, as well.
The fail-safe alternative is to write your data structure into a JSON string, which also allows you to pass the data to other applications that don't have access to your ParsedData class.
You may take a look at Intent.putExtra(String name, Parcelable object) and implement the parcelable interface in your class.
i have found the answer after all. thanks all how helped me
this is the answer:
import android.os.Parcel;
import android.os.Parcelable;
public class ParsedData implements Parcelable {
public String error;
public float protectionLevel;
public int protectionLevelColor;
public double lastBackup;
public boolean compressedType;
public Long statusSendTime;
ArrayList<Item>Items = new ArrayList<Item>();
//---------------------Constructors---------------------------
public ParsedData() { ; };
public ParsedData(Parcel in) {
readFromParcel(in);
}
//------------------------------------------------------------
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(error);
dest.writeFloat(protectionLevel);
dest.writeInt(protectionLevelColor);
dest.writeDouble(lastBackup);
dest.writeByte((byte) (compressedType ? 1 : 0));
dest.writeLong(statusSendTime);
dest.writeList(Items);
}
private void readFromParcel(Parcel in) {
error = in.readString();
protectionLevel = in.readFloat();
protectionLevelColor = in.readInt();
lastBackup = in.readDouble();
compressedType =in.readByte() == 1;
statusSendTime = in.readLong();
in.readList(Items,Item.class.getClassLoader() );
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public ParsedData createFromParcel(Parcel in) {
return new ParsedData(in);
}
public ParsedData[] newArray(int size) {
return new ParsedData[size];
}
};
}
class Item implements Parcelable {
public String name;
public float percentage;
//---------------------Constructors---------------------------
public Item() {
}
public Item(Parcel in) {
readFromParcel(in);
}
//------------------------------------------------------------
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeFloat(percentage);
}
public static final Creator<Item> CREATOR = new Creator<Item>() {
public Item createFromParcel(Parcel source) {
return new Item(source);
}
public Item[] newArray(int size) {
return new Item[size];
}
};
private void readFromParcel(Parcel in) {
this.name = in.readString();
this.percentage = in.readFloat();
}
}
and in the caller activity
ParsedData data = new PArsedData();
Intent intentBreakDown = new Intent(this,BreakDownBarActivity.class);
intentBreakDown.putExtra("data", data);
startActivity(intentBreakDown);
in the called activity(BreakDownBarActivity in my case)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.breakdownbar);
Bundle b = getIntent().getExtras();
ParsedData data = (ParsedData)b.getParcelable("data");
}

Categories

Resources