Why does Android change the value of EditTexts with same id?
There is a different possibility, just change the id of the edit text, for example,
mEditText.setId((parentView.getId()+editTextPosition+someFinalNumber));
Or if it is a EditText inside some custom Layout:
mEditText.setId((this.getId()+someFinalNumber));
In this way all EditTexts will have a different id and the text will be restored correctly.
It can simply be fixed by setting android:saveEnabled="false"
in the EditTexts Layout definition. Of course you need to make sure that the content is saved/restored yourself. So this is an non-intuitive work around -- but it works for my case. None the less the entire thing looks like an Android bug:
A nice feature of the Android layout system is that
An ID need not be unique throughout the entire tree [...]
as stated in the Android documentation. This makes code and layout reuse much simpler and is heavily used by developers. I think the save/restore instance state implementation for views uses the view's ID as the key to store it's state, hence it relies on uniqueness in the entire tree. WTF?
Update
I have added a ListView
to the example at GitHub which demonstrates that the ListView
almost certainly uses a similar workaround to prevent EditTexts to run into this problem. As can be seen, text which is entered into an EditText inside a ListView is not automatically restored.
Greetings from the future. Sadly Android framework developers still like to laugh at our expense. Thanks to our bright scientists, we have come up with a bit better solution. See below (yes we still use java in the future). You can find the original "research paper" here.
private static final String KEY_SUPER = "superState";
private static final String KEY_CHILD = "childState";
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
// Don't call super, to disable saving child view states
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
// Similar as above, but other way around
dispatchThawSelfOnly(container);
}
@Override
protected @Nullable Parcelable onSaveInstanceState() {
val bundle = new Bundle();
val superState = super.onSaveInstanceState();
bundle.putParcelable(KEY_SUPER, superState);
val childStates = saveChildViewStates();
bundle.putSparseParcelableArray(KEY_CHILD, childStates);
return bundle;
}
private SparseArray<Parcelable> saveChildViewStates() {
val childViewStates = new SparseArray<Parcelable>();
for(val view : getChildViews())
view.saveHierarchyState(childViewStates);
return childViewStates;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
val bundle = (Bundle) state;
val superState = bundle.getParcelable(KEY_SUPER);
super.onRestoreInstanceState(superState);
val childState = bundle.getSparseParcelableArray(KEY_CHILD);
restoreChildViewStates(childState);
}
private void restoreChildViewStates(SparseArray<Parcelable> states) {
for(val view : getChildViews())
view.restoreHierarchyState(states);
}
private Iterable<View> getChildViews() {
int childCount = getChildCount();
return () -> new Iterator<View>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < childCount;
}
@Override
public View next() {
val view = getChildAt(index);
if(view == null) {
throw new RuntimeException("View was null. Index: " + index +
" count: " + childCount + ".");
}
Objects.requireNonNull(view);
index++;
return view;
}
};
}
PS: Note that this solution can misbehave, if you order of child View
s changes. Bob's uncle says it's okay for what we need right now. I leave it for future scientists to build upon this.
PPS: If you're working at google, feel free to copy paste it into framework and build on it.
PPPS: You might wanna use better app architecture, like MVVM.