Почему Android меняет значение EditTexts с тем же идентификатором?

У меня есть фрагмент, содержащий LinearLayout, где раздуваются различные элементы в зависимости от бизнес-логики. Один из этих элементов содержит EditText. Когда у меня есть несколько из этих элементов с различным содержанием, и я отсоединяю / прикрепляю фрагмент, все EditTexts так или иначе получают все тот же текст. Это происходит только до тех пор, пока EditText имеет идентификатор в файле макета.

Почему это происходит? Есть ли другой способ предотвратить это, кроме удаления идентификатора? Я хотел бы использовать findViewById на мои раздутые предметы, чтобы получить доступ к представлениям вместо подверженных ошибкам getChildAt,

Я создал минималистичный пример для демонстрации проблемы по адресу https://github.com/rodja/EditTextValueProblem

2 ответа

Решение

Это можно просто исправить, установив android:saveEnabled="false" в определении макета EditTexts. Конечно, вы должны убедиться, что контент сохранен / восстановлен самостоятельно. Так что это не интуитивный обходной путь - но он работает для моего случая. Тем не менее, все это выглядит как ошибка Android:

Приятной особенностью системы разметки Android является то, что

Идентификатор не обязательно должен быть уникальным по всему дереву [...]

как указано в документации Android. Это значительно упрощает повторное использование кода и макета и активно используется разработчиками. Я думаю, что реализация состояния экземпляра сохранения / восстановления для представлений использует идентификатор представления в качестве ключа для сохранения его состояния, поэтому оно опирается на уникальность во всем дереве. WTF?

Обновить

Я добавил ListView к примеру на GitHub, который демонстрирует, что ListView почти наверняка использует подобный обходной путь, чтобы не допустить столкновения EditTexts с этой проблемой. Как видно, текст, который вводится в EditText внутри ListView, не восстанавливается автоматически.

Существует другая возможность, просто измените идентификатор редактируемого текста, например,

mEditText.setId((parentView.getId()+editTextPosition+someFinalNumber));

Или, если это EditText внутри некоторого пользовательского макета:

mEditText.setId((this.getId()+someFinalNumber));

Таким образом, все EditTexts будут иметь разные идентификаторы, и текст будет восстановлен правильно.

Привет из будущего. К сожалению, разработчики фреймворков Android до сих пор любят посмеяться над нашим счетом. Благодаря нашим умным ученым мы пришли к более эффективному решению. См. Ниже (да, мы все еще будем использовать java в будущем). Вы можете найти оригинальную "исследовательскую работу" здесь.

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: Обратите внимание, что это решение может плохо себя вести, если вы заказываете ребенка Views изменения. Дядя Боба говорит, что сейчас все в порядке. Я оставляю будущим ученым развивать это.

PPS: Если вы работаете в Google, не стесняйтесь копировать и вставлять его во фреймворк и строить на нем.

PPPS: возможно, вы захотите использовать лучшую архитектуру приложения, например MVVM.

Другие вопросы по тегам