Сохранение состояния с помощью фрагментов

Различные системные операции Android могут повлиять на состояние вашего фрагмента. Чтобы обеспечить сохранение состояния пользователя, фреймворк Android автоматически сохраняет и восстанавливает фрагменты и обратный стек. Поэтому вам нужно убедиться, что любые данные в вашем фрагменте также сохраняются и восстанавливаются.

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

  • Переменные: локальные переменные во фрагменте.
  • Состояние представления – любые данные, принадлежащие одному или нескольким представлениям во фрагменте.
  • SavedState: данные, присущие этому экземпляру фрагмента, которые должны быть сохранены в onSaveInstanceState().
  • NonConfig – данные, полученные из внешнего источника, например сервера или локального хранилища, или данные, созданные пользователем и отправленные на сервер после фиксации.

Часто переменные обрабатываются так же, как и SavedState, но в следующей таблице они различаются, чтобы продемонстрировать влияние различных операций на каждую из них.

Операция Переменные Просмотр состояния SavedState NonConfig
Добавлено в обратный стек ✓ ✓ x ✓
Изменение конфигурации x ✓ ✓ ✓
Завершение и возобновление процесса x ✓ ✓ ✓*
Удаленный фрагмент не добавлен в обратный стек x x x x
Организатор завершил сеанс x x x x

* Состояние NonConfig можно сохранить при завершении процесса с помощью модуля Saved State для ViewModel.

Таблица 1. Различные деструктивные операции с фрагментами и их влияние на разные типы состояний.

Рассмотрим конкретный пример. Рассмотрим экран, на котором генерируется случайная строка, отображается в TextView и предоставляется возможность отредактировать строку перед отправкой другу:

приложение для генерации случайного текста, которое демонстрирует различные типы состояний
Рисунок 1. Приложение для генерации случайного текста, которое демонстрирует различные типы состояний.

В этом примере предположим, что после нажатия кнопки редактирования приложение показывает представление EditText, в котором пользователь может изменить сообщение. Если пользователь нажмет Отмена, представление EditText должно быть очищено, а его видимость должна быть установлена на View.GONE. Для бесперебойной работы такого экрана может потребоваться управление четырьмя фрагментами данных:

Данные Тип Тип состояния Описание
seed Long NonConfig Начальное значение, используемое для случайной генерации нового доброго дела. Генерируется при создании ViewModel.
randomGoodDeed String SavedState + Variable Генерируется при первом создании фрагмента. randomGoodDeed сохраняется, чтобы пользователи видели одно и то же доброе дело даже после завершения и перезапуска процесса.
isEditing Boolean SavedState + Variable Логический флаг, для которого устанавливается значение true, когда пользователь начинает редактирование. isEditing сохраняется, чтобы при повторном создании фрагмента область редактирования оставалась видимой.
Измененный текст Editable Просмотр состояния (владелец: EditText) Отредактированный текст в представлении EditText. Представление EditText сохраняет этот текст, чтобы пользователь не потерял внесенные изменения.

Таблица 2. Состояния, которыми должно управлять приложение для генерации случайного текста.

В следующих разделах описывается, как правильно управлять состоянием данных с помощью деструктивных операций.

Состояние представления

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

Например, в предыдущем сценарии отредактированная строка хранится в EditText. Объект EditText знает значение отображаемого текста, а также другие сведения, например начало и конец выделенного текста.

Чтобы сохранить состояние представления, ему нужен идентификатор. Этот идентификатор должен быть уникальным в пределах фрагмента и его иерархии View. Представления без идентификатора не могут сохранять свое состояние.

<EditText
    android:id="@+id/good_deed_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Как указано в таблице 1, представления сохраняют и восстанавливают свое состояние ViewState во время всех операций, которые не удаляют фрагмент и не уничтожают хост.

SavedState

Фрагмент отвечает за управление небольшими объемами динамического состояния, которые являются неотъемлемой частью его функционирования. Для сохранения легко сериализуемых данных можно использовать Fragment.onSaveInstanceState(Bundle). Как и в случае с Activity.onSaveInstanceState(Bundle), данные, которые вы помещаете в пакет, сохраняются при изменении конфигурации, завершении процесса и повторном создании и доступны в методах onCreate(Bundle), onCreateView(LayoutInflater, ViewGroup, Bundle) и onViewCreated(View, Bundle) фрагмента.

Продолжая предыдущий пример, randomGoodDeed – это документ, который показывается пользователю, а isEditing – это флаг, определяющий, показывает или скрывает фрагмент EditText. Сохраненное состояние должно быть сохранено с помощью onSaveInstanceState(Bundle), как показано в следующем примере:

Kotlin

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putBoolean(IS_EDITING_KEY, isEditing)
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed)
}

Java

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(IS_EDITING_KEY, isEditing);
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed);
}

Чтобы восстановить состояние в onCreate(Bundle), извлеките сохраненное значение из пакета:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    isEditing = savedInstanceState?.getBoolean(IS_EDITING_KEY, false)
    randomGoodDeed = savedInstanceState?.getString(RANDOM_GOOD_DEED_KEY)
            ?: viewModel.generateRandomGoodDeed()
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        isEditing = savedInstanceState.getBoolean(IS_EDITING_KEY, false);
        randomGoodDeed = savedInstanceState.getString(RANDOM_GOOD_DEED_KEY);
    } else {
        randomGoodDeed = viewModel.generateRandomGoodDeed();
    }
}

Как указано в таблице 1, переменные сохраняются, когда фрагмент помещается в стек возврата. Если вы будете обрабатывать их как сохраненное состояние, они не будут удаляться при деструктивных операциях.

NonConfig

Данные NonConfig должны размещаться за пределами фрагмента, например в ViewModel. В предыдущем примере seed (состояние NonConfig) генерируется в ViewModel. Логика поддержания его состояния принадлежит ViewModel.

Kotlin

public class RandomGoodDeedViewModel : ViewModel() {
    private val seed = ... // Generate the seed

    private fun generateRandomGoodDeed(): String {
        val goodDeed = ... // Generate a random good deed using the seed
        return goodDeed
    }
}

Java

public class RandomGoodDeedViewModel extends ViewModel {
    private Long seed = ... // Generate the seed

    private String generateRandomGoodDeed() {
        String goodDeed = ... // Generate a random good deed using the seed
        return goodDeed;
    }
}

Класс ViewModel позволяет сохранять данные при изменении конфигурации, например при повороте экрана, и остается в памяти, когда фрагмент помещается в обратный стек. После завершения процесса и его повторного запуска ViewModel создается заново и генерируется новый seed. Добавление модуля SavedState в ViewModel позволяет ViewModel сохранять простое состояние при завершении и повторном создании процесса.

Дополнительные ресурсы

Дополнительную информацию об управлении состоянием фрагментов можно найти в следующих ресурсах.

Практические работы

Руководства