18

Assume you want to derive your own View class from an existing View implementation, adding a bit of value, hence maintaining a few variables which represent your View's state in a meaningful way.

It would be nice if your View would save its state automatically just like others do (if an ID is assigned) so you would want to override onRestoreInstanceState() and onSaveInstanceState().

Of course, you need to call the respective methods of your base class, and you need to combine your state information with that of your base class.

Obviously, the only safe way to do so is to wrap your super class' Parcelable in an own Parcelable such that the keys won't get mixed up.

Now there's View.BaseSavedState and its interesting getSuperState() method but I somehow fail to understand how this really adds value to just storing the base class' Parcelable in a Bundle along with the derived View's state values and return that. On the other hand, maybe some other system component will expect all InstanceState information to be of type View.AbsSavedState (e.g. such that getSuperState() can be called)?

Any experiences you're willing to share?

class stacker
  • 5,357
  • 2
  • 32
  • 65

2 Answers2

16

To complement James Chen's answer, here is a full example of how to use this method, based on blog article by Charles Harley.

Code from the link:

public class LockCombinationPicker extends LinearLayout {
    private NumberPicker numberPicker1;
    private NumberPicker numberPicker2;
    private NumberPicker numberPicker3;

    public LockCombinationPicker(Context context) {
        this(context, null);
    }

    public LockCombinationPicker(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LockCombinationPicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        loadViews();
    }

    private void loadViews() {
        LayoutInflater.from(getContext()).inflate(R.layout.lock_combination_picker, this, true);
        numberPicker1 = (NumberPicker) findViewById(R.id.number1);
        numberPicker1.setMinValue(0);
        numberPicker1.setMaxValue(10);
        numberPicker2 = (NumberPicker) findViewById(R.id.number2);
        numberPicker2.setMinValue(0);
        numberPicker2.setMaxValue(10);
        numberPicker3 = (NumberPicker) findViewById(R.id.number3);
        numberPicker3.setMinValue(0);
        numberPicker3.setMaxValue(10);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        return new SavedState(superState, numberPicker1.getValue(), numberPicker2.getValue(), numberPicker3.getValue());
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        numberPicker1.setValue(savedState.getNumber1());
        numberPicker2.setValue(savedState.getNumber2());
        numberPicker3.setValue(savedState.getNumber3());
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        // As we save our own instance state, ensure our children don't save and restore their state as well.
        super.dispatchFreezeSelfOnly(container);
    }

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        /** See comment in {@link #dispatchSaveInstanceState(android.util.SparseArray)} */
        super.dispatchThawSelfOnly(container);
    }

    /**
     * Convenience class to save / restore the lock combination picker state. Looks clumsy but once created is easy to maintain and use.
     */
    protected static class SavedState extends BaseSavedState {
        private final int number1;
        private final int number2;
        private final int number3;

        private SavedState(Parcelable superState, int number1, int number2, int number3) {
            super(superState);
            this.number1 = number1;
            this.number2 = number2;
            this.number3 = number3;
        }

        private SavedState(Parcel in) {
            super(in);
            number1 = in.readInt();
            number2 = in.readInt();
            number3 = in.readInt();
        }

        public int getNumber1() {
            return number1;
        }

        public int getNumber2() {
            return number2;
        }

        public int getNumber3() {
            return number3;
        }

        @Override
        public void writeToParcel(Parcel destination, int flags) {
            super.writeToParcel(destination, flags);
            destination.writeInt(number1);
            destination.writeInt(number2);
            destination.writeInt(number3);
        }

        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
kinORnirvana
  • 1,667
  • 2
  • 17
  • 22
  • 1
    Thank you for poining out the need to pass actual data (numbers 1-3 in your case) to the `SavedState` constructor - it is not clear from the accepted answer. However, I would advice to pass the instance of `LockCombinationPicker` instead. First, `SavedState` is an inner class, so it has access to private fields of `LockCombinationPicker` anyway. Second, if you needed to store values of 30 different fields, you would not bloat your constructor with 30 arguments. – Alex Semeniuk Aug 23 '19 at 10:12
15

I think the design needs us, and as the name implies, to implement a subclass of View.BaseSavedState to store values by overriding Parcelable's interface.

TextView.SavedState is a good example

public static class SavedState extends BaseSavedState {
    int selStart;
    int selEnd;
    CharSequence text;
    boolean frozenWithFocus;
    CharSequence error;

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeInt(selStart);
        out.writeInt(selEnd);
        out.writeInt(frozenWithFocus ? 1 : 0);
        TextUtils.writeToParcel(text, out, flags);

        if (error == null) {
            out.writeInt(0);
        } else {
            out.writeInt(1);
            TextUtils.writeToParcel(error, out, flags);
        }
    }

    @Override
    public String toString() {
        String str = "TextView.SavedState{"
                + Integer.toHexString(System.identityHashCode(this))
                + " start=" + selStart + " end=" + selEnd;
        if (text != null) {
            str += " text=" + text;
        }
        return str + "}";
    }

    @SuppressWarnings("hiding")
    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];
        }
    };

    private SavedState(Parcel in) {
        super(in);
        selStart = in.readInt();
        selEnd = in.readInt();
        frozenWithFocus = (in.readInt() != 0);
        text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);

        if (in.readInt() != 0) {
            error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        }
    }
}
James Chen
  • 305
  • 3
  • 7
  • Thank you! I never looked at some example code in this case. This has made everything much clearer for me. – class stacker Apr 25 '13 at 04:11
  • writeToParcel never seems to be called by onSaveInstanceState. What is it purpose? – dylan7 Jan 20 '16 at 23:53
  • @dylan7 - It's called by the framework when serializing the state. – Ted Hopp Aug 26 '16 at 02:36
  • 1
    You should go further and include the code for both `onSaveInstanceState()` and `onRestoreInstanceState()` methods of `TextView` class. Some important things are happening there, which are crucial for understanding the entire concept (people can look up themselves, of course, but still...) – Alex Semeniuk Aug 23 '19 at 10:04