Challenge: Saving State Using View.BaseSavedState


#1

As far as I know, we can use Bundle or implement a Parcelable class to save custom view state. Which one is better depends on circumstance.

For this challenge, if we want to use the Bundle, we can make the Box class implementing Serializable, but this way turns out to be a bad approach. When we mark the Box as Serializable, we also have to make all of Box’s variables Serializable as well. You may notice that the instance variable mOrigin and mCurrent are implemented from Parcelable and mark them with keyword transient to get around the Serialization, but the value store in the variable will be lost. (Look @ maximal93’ answer at the Challenge “saving state” - NotSerializableException error for more detail)

Further more, some students may make the Box class implementing Parcelable and use the Bundle to save the state. ( See the @icibtbt answer at the Challenge “saving state” - NotSerializableException error) , but this is not a good practice. Instead we can use View.BaseSavedState which implements Parcelable to directly tackle the issue without using the Bundle. And it turns out to be more elegant!

In BoxDrawingView.java

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.mBoxList = mBoxen;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        mBoxen = ss.mBoxList;
    }

    private static class SavedState extends BaseSavedState {
        private List<Box> mBoxList ; 

        public SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            mBoxList = (ArrayList<Box>)in.readValue(getClass().getClassLoader());
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeValue(mBoxList);
        }

        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];
            }
        };
    }

Check the Link http://www.intertech.com/Blog/android-custom-view-tutorial-part-3-saving-state/ and http://cyrilmottier.com/2014/09/25/deep-dive-into-android-state-restoration/ for more detail.

In short, if the values we want to save consist only of Primitive type, using the Bundle is the easiest way, otherwise using the View.BaseSavedState.


#2

Hello, I had a try with you solution. Everything seems ok. The mBoxen would be saved and restored. However, i had a test on writeToParcel(…) method via callint it in onSaveInstanceState() method, the code look like this:

...
Parcel p = Parcel.obtain();
        p.writeInt(99);
        BoxDrawingViewState bs = new BoxDrawingViewState(superState);
        bs.mBoxList = mBoxen;
        bs.writeToParcel(p, 0);
...

there would be a RuntimeException:

 java.lang.RuntimeException: Parcel: unable to marshal value com.ldb.android.example.draganddraw.Box@1c5b2fb0

Because in the method Parcel.writeList(…)

public final void writeList(List val) {
        ...
        while (i < N) {
            writeValue(val.get(i));
            i++;
        }
    }

the val.get(i) return the Box instance, and then the Box instance can’t match a type in the method Parcel.writeValue(…). Finally, there is a RuntimeException.
So, There may be a bug.
The reason why i had the test is because i don’t kown what the use of writeToParcel(…), i can’t find where it would be called. I commented the content in it, the App can also run well. Could you write more about writeToParcel(…)? Thanks!


#3

I found out the rest of the code in SavedState was never been called so I commented out all of them and leaving only

private static class SavedState extends BaseSavedState { 
private List <Box> mBoxList ;
public SavedState(Parcelable superState) {
 super(superState);
 }

It turns out worked just fine and that leaves the question why is it there to begin with?