This is follow up to: Android local variable get's lost when using camera intent
Proper way to do it is to handle onSaveInstanceState and onRestoreInstanceState like shown here:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/CompoundButton.java
Here is my code:
static class SavedState extends BaseSavedState
{
private String requestedFileName;
private UUID[] mImages = new UUID[4];
SavedState(Parcelable superState)
{
super(superState);
}
private SavedState(Parcel in)
{
super(in);
}
#Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
}
//required field that makes Parcelables from a Parcel
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>()
{
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
#Override
public Parcelable onSaveInstanceState()
{
//begin boilerplate code that allows parent classes to save state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
//end
ss.requestedFileName = this.requestedFileName;
ss.mImages = this.mImages;
return ss;
}
#Override
public void onRestoreInstanceState(Parcelable state)
{
//begin boilerplate code so parent classes can restore state
if(!(state instanceof SavedState))
{
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
//end
this.requestedFileName = ss.requestedFileName;
this.mImages = ss.mImages;
RestoreImageState();
}
Now to my question. This code seems to work properly and it handles all state changes without a problem. HOWEVER, if you look at SavedState.writeToParcel and SavedState.SavedState you will notice that I do not store my variables there. Do I have to? Why? Problem is that I understand how to wrteToParcel and my data types match. But reading out of parcel not so clear with complex types. And in my tests it wasn't called.
EDIT:
Does this look correct for save/retreive order?
private SavedState(Parcel in)
{
super(in);
this.mName = in.readString();
this.mIndex = in.readInt();
this.mApplicableDamages = (String[])in.readValue(null);
this.mSelectedDamages = (boolean[])in.readValue(null);
}
#Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeString(this.mName);
out.writeInt(this.mIndex);
out.writeArray(this.mApplicableDamages);
out.writeBooleanArray(this.mSelectedDamages);
}
You have to save these variables in onSaveInstanceState() and restore them in onRestoreInstanceState() later if they're part of your view's state and you don't want to lose them when a parent activity is recreated.
The variables may be lost because when the configuration change happens the parent activity is destroyed and a new activity object is recreated. This new object is receive the previously saved state. And if you don't add some variables to the state the won't be restored.
As to the writing complex types to and reading the from Parcel you can implement Parcelable for some parts of the complex type. Or you can just decompose the complex type to the primitive (parcelable) fields and store this fields one by one. In your case UUID[] can be stored in Parcel as Serializable or you can convert UUID[] to ParcelUuid[] and store this array as parcelable array.
And a few words about SavedState implementation. The fields in SavedState will not be saved if you don't add them to Parcel. So you have to write the to Parcel in SavedState.writeToParcel() method. And also you need to read them back in SavedState(Parcel) constructor.
static class SavedState extends BaseSavedState
{
private String requestedFileName;
private UUID[] mImages = new UUID[4];
SavedState(Parcelable superState)
{
super(superState);
}
private SavedState(Parcel in)
{
super(in);
requestedFileName = in.readString();
mImages = (UUID[])in.readSerializable();
}
#Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeString(requestedFileName);
out.writeSerializable(mImages);
}
//required field that makes Parcelables from a Parcel
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>()
{
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
Related
in Settings documentation, there is an implementation of onSaveInstanceState and onRestoreInstanceState with a class called SaveSatet.
how does it work? can anyone explain to me?
private static class SavedState extends BaseSavedState {
// Member that holds the setting's value
// Change this data type to match the type saved by your Preference
int value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readInt(); // Change this to read the appropriate data type
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeInt(value); // Change this to write the appropriate data type
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};}
and the implementation:
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
// Check whether this Preference is persistent (continually saved)
if (isPersistent()) {
// No need to save instance state since it's persistent,
// use superclass state
return superState;
}
// Create instance of custom BaseSavedState
final SavedState myState = new SavedState(superState);
// Set the state's value with the class member that holds current
// setting value
myState.value = mNewValue;
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
// Check whether we saved the state in onSaveInstanceState
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// Cast state to custom BaseSavedState and pass to superclass
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// Set this Preference's widget to reflect the restored state
mNumberPicker.setValue(myState.value);
}
I am using the GCM network manager and I want to pass the service (specifically to the onRunTask(TaskParams taskParams) some objects. From the documentation taskParams are simply a string and a bundle but I want to pass more complex objects.
How can this be done?
Thank you!
One way is to have your custom object implement the Parcelable interface and use Bundle.putParcelable/Bundle.getParcelable.
It requires a little more effort to use than using Java's native serialization, but it's way faster (and I mean way, WAY faster).
For example:
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();
}
}
Also you can read Parcelable vs Serializable
I am implementing Parcelable in order to transmit some simple data throughout an Intent.
However, There is one method in the Parcelable interface that I don't understand at all : newArray().
It does not have any relevant documentation & is not even called in my code when I parcel/deparcel my object.
Sample Parcelable implementation :
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();
}
}
So, my question is : what is this method for ? and when is it called ?
Is there any point in doing something else than return new MyParcelable[size]; in that method ?
this is a function to be called when you try to deserialize an array of Parcelable objects and for each single object createFromParcel is called.
It is there to prepare the typed array without all the generics stuff. That's it.
Returning just the standard return new MyParcelable[size]; is fine.
It is normal, that you never call it yourself. However, by calling something like Bundle.getParcelableArray() you end up in this method indirectly.
newArray is responsible to create an array of our type of the appropriate size
I have created a custom View / Compound Control in my Android application, which lets users "draw" their signature.
The signature is contained in a Canvas, linked to a Bitmap object.
I want the signature "image" to be preserved when the orientation is changed. Therefore, I have implemented onSaveInstanceState / onRestoreInstanceState by storing the Bitmap's pixels:
#Override
public Parcelable onSaveInstanceState()
{
// Call superclass method, retrieve Parcelable
Parcelable superState = super.onSaveInstanceState();
if (pictureBitmap_ != null)
{
// Retrieve the current pixels array.
int totalSize = pictureBitmap_.getWidth()
* pictureBitmap_.getHeight();
int pixels[] = new int[totalSize];
pictureBitmap_.getPixels(pixels,
0,
pictureBitmap_.getWidth(),
0,
0,
pictureBitmap_.getWidth(),
pictureBitmap_.getHeight());
// Create the saved state object.
SavedState savedState = new SavedState(superState);
savedState.pixels = pixels;
// Return the populated saved state object.
return savedState;
}
else
{
// Simply pass original Parcelable along
return superState;
}
}
#Override
public void onRestoreInstanceState(Parcelable state)
{
// Is the Parcelable an instance of our custom SavedState?
if (!(state instanceof SavedState))
{
// No, simply delegate to superclass.
super.onRestoreInstanceState(state);
return;
}
// Retrieve custom state object.
SavedState savedState = (SavedState) state;
// Use superclass to restore original state.
super.onRestoreInstanceState(savedState.getSuperState());
// Restore custom state (transfer pixels).
this.transferPixels_ = savedState.pixels;
// If the picture bitmap is already initialized...
if (pictureBitmap_ != null)
{
// Transfer the pixels right away.
transferBitmap();
// Refresh the canvas
drawSignature();
}
}
The integer array of pixels is stored in a SavedState object, which is a custom subclass of BaseSavedState:
private static class SavedState
extends BaseSavedState
{
// Members
int[] pixels;
// Methods
SavedState(Parcelable superState)
{
super(superState);
}
private SavedState(Parcel in)
{
super(in);
in.readIntArray(pixels);
}
#Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeIntArray(pixels);
}
// required field that makes Parcelables from a Parcel
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
Now, while this is working well if I only have ONE (1) instance of my custom view in my activity... a problem occurs if I have TWO (2) or more instances: when I flip the orientation, all my controls end up displaying the same signature, which was the last control's signature.
In other words, the last set of pixels stored in a SavedState object appears to become the "universal" set of pixels for all my control instances within my Activity.
How do I ensure that each control has its own independent saved state?
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.