How do you save view widget instance state when, by using XML-defined widget layouts, components of the individual widget instances all have the same ID?
Take, for example, the NumberPicker
widget that's used in the TimePicker
widget (note that NumberPicker
is not exposed to the SDK). This is a simple widget with three components which are inflated from number_picker.xml
: one increment button, one decrement button, and one EditText
where you can directly enter a number. In order for the code to interact with these widgets, they all have IDs (R.id.increment
, R.id.decrement
and R.id.timepicker_input
respectively).
Let's say you have three NumberPicker
s in an XML layout and you give them distinct IDs (eg. R.id.hour
, R.id.minute
).¹ This layout is then inflated to the content view of an activity. We decide to change the activity's orientation, so Activity.onSaveInstanceState(Bundle)
helpfully saves our view state for each view that has an ID (this is the default behavior).
Unfortunately, the three NumberPicker
s have EditText
s that all share the same ID — R.id.timepicker_input
. Thus, when the activity is restored, the one furthest down in the view hierarchy is the one whose state seems to be preserved for all three of them. Additionally, the focus goes to the first NumberPicker
when restored regardless of which one had focus when saved.
TimePicker
gets around this problem by preserving the state itself, separately. Unfortunately, this won't preserve the cursor position or the focused view without a lot more work. I'm not sure how it preserves that state if it does at all (and quickly playing with a time input dialog seems to indicate that it can somehow).
Please see the sample code to demonstrate this issue: https://github.com/xxv/AndroidNumberPickerBug
¹ In the view hierarchy, this sets the ID of the LinearLayout
that NumberPicker
extends to your IDs.
I stumbled across the same issue when trying to create my own compound view. From looking at the Android source code I believe the correct way to implement compound views is for the compound view itself to take on the responsibility of saving and restoring the instance state of its children and to prevent the calls to save and restore instance state from being passed onto the child views. This works around the issue of the IDs of child views not being unique when you have more then one instance of the same compound view in an activity.
This might sound complicated but it is really quite easy and the APIs actually make provision for this exact scenario. I've written a blog post here on how this is done but essentially in your compound view you need to implement the following 4 methods, customising onSaveInstanceState() and onRestoreInstanceState() to meet your specific requirements.
@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 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 container) {
/** See comment in {@link #dispatchSaveInstanceState(android.util.SparseArray)} */
super.dispatchThawSelfOnly(container);
}
Regarding the problem with NumberPicker/TimePicker, as mentioned in another comment there appears to be a bug with NumberPicker and TimePicker. To fix it you could override both and implement the solution I've described.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With