Не удается сохранить вложенные фрагменты

Есть ли другой способ сохранить состояние вложенного фрагмента? Или если мы не должны этого делать, то почему? Спасибо !

02-13 11:42:43.258: E/AndroidRuntime(7167): java.lang.IllegalStateException: Can't retain fragements that are nested in other fragments
02-13 11:42:43.258: E/AndroidRuntime(7167):     at android.support.v4.app.Fragment.setRetainInstance(Fragment.java:742)

person Bogdan Zurac    schedule 13.02.2013    source источник
comment
Хм. Я не получаю исключения, как вы. Для меня onCreateView в родительском просто получает новый дочерний фрагмент от findFragmentById. Это означает, что его необходимо повторно инициализировать, что очень медленно в случае Google MapFragment. :(   -  person aij    schedule 21.02.2015
comment
Обратите внимание, что после исправления в коде это больше не является проблемой. .google.com/p/android/issues/detail?id=197271   -  person Cheok Yan Cheng    schedule 04.07.2016
comment
Он выпущен в библиотеке поддержки 24   -  person Cheok Yan Cheng    schedule 04.07.2016
comment
Это все еще проблема.   -  person Mitch    schedule 04.10.2019


Ответы (4)


Вы можете использовать FragmentManager.saveFragmentInstanceState(Fragment) для получения состояния фрагмента. Возвращаемое значение реализует Parcelable, поэтому вы можете поместить его в Bundle.

Для восстановления вы можете указать состояние после создания фрагмента, используя Fragment.setInitialSavedState(Fragment.SavedState).

person nicopico    schedule 13.02.2013
comment
Глядя на исходный код, это не решает эту проблему. setRetainInstance позволяет сохранять произвольные поля класса, даже поля, не поддающиеся разбору и сериализации. Однако этот метод просто вызывает onSaveInstanceState(Bundle) для вашего фрагмента, что означает, что все, что вы можете сделать, это сохранить то, что вы можете поместить в Bundle. - person Matthias; 22.10.2013
comment
@Matthias Действительно, но в большинстве случаев это именно то, что вам нужно. setRetainInstance часто используется вместо реализации надлежащего управления состоянием. - person nicopico; 22.10.2013
comment
Насколько я видел, вам на самом деле не нужно сохранять внутренние фрагменты, если вы установите true для родительского фрагмента. Почему ? Потому что это также автоматически сохранит детей. - person Bogdan Zurac; 30.10.2013
comment
@ Андрей, поэтому при попытке setRetainInstance внутри вложенного фрагмента вы получите: unable to retain the instance of the children of nested fragments - person S.Thiongane; 24.10.2015

Начиная с библиотеки поддержки 20+ (https://code.google.com/p/android/issues/detail?id=74222), есть ошибка с пересозданием экземпляра для дочерних фрагментов, для этого есть исправление - http://ideaventure.blogspot.com.au/2014/10/вложенный-сохраненный-фрагмент-потерянный-состояние.html

Код с веб-страницы (добавьте это в свой родительский фрагмент) -

private FragmentManager childFragmentManager() {//!!!Use this instead of getFragmentManager, support library from 20+, has a bug that doesn't retain instance of nested fragments!!!!
        if(mRetainedChildFragmentManager == null) {
            mRetainedChildFragmentManager = getChildFragmentManager();
        }
        return mRetainedChildFragmentManager;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (mRetainedChildFragmentManager != null) {
            //restore the last retained child fragment manager to the new
            //created fragment
            try {
                Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
                childFMField.setAccessible(true);
                childFMField.set(this, mRetainedChildFragmentManager);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

    }
person Mikelis Kaneps    schedule 28.01.2015
comment
То же, что и выше, не используйте отражение, пожалуйста. Или хотя бы доказать, что это единственный путь. - person NecipAllef; 18.11.2015
comment
@NecipAllef, то же, что и выше - если вы говорите что-то плохое - предоставьте лучшее решение. И минусуйте, только если знаете, что он существует. - person Luten; 18.11.2015

После этого фиксации AOSP это больше не является ограничением последней версии библиотеки поддержки. .

Ниже приведено сообщение фиксации:

Разрешить setRetainInstance(true) для вложенных фрагментов

Сохраняйте произвольно вложенные фрагменты изменений конфигурации как объекты, не являющиеся объектами конфигурации. Это позволяет использовать дочерние фрагменты с сохранением экземпляра в качестве произвольных непрозрачных зависимостей внутри других фрагментов.

person hidro    schedule 19.10.2016
comment
В основном это правда, новое* обновление библиотеки поддержки работает на 7.0 и 5.0 и ниже, но на Android 6.0 работает некорректно. Стратегия использования сохраняемых фрагментов станет нормой, поскольку в версии 7.0 вы сохраняете только 1 МБ данных через onSaveInstance. - person Darxval; 29.01.2017

Проблема: mChildFrgamentManager создается повторно (https://code.google.com/p/android/issues/detail?id=74222)
Временное решение: сохранение mChildFrgamentManager, если фрагмент имеет setRetainInstance(true):

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    if (getRetainInstance()) {
        if (mRetainedChildFragmentManager != null) {
            try {
                Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
                childFMField.setAccessible(true);
                childFMField.set(this, mRetainedChildFragmentManager);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } else {
            mRetainedChildFragmentManager = getChildFragmentManager();
        }
    }
}

Внимание! С этим кодом setRetainInstace следует вызывать до onAttach.

P.S. Это немного улучшенная версия ответа @attels.

person Luten    schedule 05.08.2015
comment
@akshayrajkore что значит раньше? Это onAttach метод фрагмента. - person Luten; 27.10.2015
comment
Не используйте отражение! - person NecipAllef; 17.11.2015
comment
@NecipAllef Почему? Если у вас есть обходной путь без размышлений - предложите его. Если нет - уберите минус, пожалуйста. - person Luten; 18.11.2015
comment
@akshayrajkore, я неправильно понял твой вопрос. setRetainInstance следует вызывать в конструкторе фрагмента. - person Luten; 18.11.2015
comment
@NecipAllef Почему я должен использовать Activity вместо Fragment? Вопрос не в том, что использовать, а в том, как решить проблему. - person Luten; 18.11.2015
comment
Использование Activity не имеет смысла, извините, я удалил свой комментарий. Но отражение не следует считать решением. При условии, что ответ уже есть, я думаю, вам следует удалить свой ответ. - person NecipAllef; 18.11.2015
comment
@NecipAllef, это решение проблемы - это факт, потому что он решает проблему. Насколько хорошо это решение - другой вопрос. Поскольку лучших решений не предоставлено - очень плохой смысл отрицать рабочие решения. Особенно, если вы даже не понимаете вопроса. - person Luten; 18.11.2015