Модуль сохранения состояния для ViewModel (представлений) — часть Android Jetpack .

Концепции и реализация Jetpack Compose

Как упоминалось в разделе «Сохранение состояний пользовательского интерфейса» , объекты ViewModel могут обрабатывать изменения конфигурации, поэтому вам не нужно беспокоиться о состоянии при повороте экрана или в других случаях. Однако, если вам необходимо обрабатывать завершение процесса, инициированное системой, вы можете использовать API SavedStateHandle в качестве резервного варианта.

Состояние пользовательского интерфейса обычно хранится или упоминается в объектах ViewModel , а не в активностях, поэтому использование onSaveInstanceState() требует некоторого шаблонного кода, который модуль сохранения состояния может обработать за вас.

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

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

Настраивать

Начиная с версии Fragment 1.2.0 или её транзитивной зависимости Activity 1.1.0 , вы можете принимать SavedStateHandle в качестве аргумента конструктора для вашей ViewModel .

Котлин

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

После этого вы можете получить экземпляр вашей ViewModel без какой-либо дополнительной настройки. Фабрика ViewModel по умолчанию предоставляет вашему ViewModel соответствующий SavedStateHandle .

Котлин

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

Java

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...

    }

    ...
}

При предоставлении пользовательского экземпляра ViewModelProvider.Factory вы можете включить использование SavedStateHandle , расширив AbstractSavedStateViewModelFactory .

Работа с SavedStateHandle

Класс SavedStateHandle представляет собой карту типа «ключ-значение», которая позволяет записывать и извлекать данные из сохраненного состояния с помощью методов set() и get() .

Использование SavedStateHandle позволяет сохранить значение запроса после завершения процесса, гарантируя, что пользователь увидит тот же набор отфильтрованных данных до и после повторного запуска, без необходимости вручную сохранять, восстанавливать и передавать это значение обратно в ViewModel для активности или фрагмента.

SavedStateHandle также имеет другие методы, которые вы можете ожидать при взаимодействии с картой ключ-значение:

  • contains(String key) - Проверяет, существует ли значение для заданного ключа.
  • remove(String key) - Удаляет значение для заданного ключа.
  • keys() - Возвращает все ключи, содержащиеся в SavedStateHandle .

Кроме того, вы можете получать значения из SavedStateHandle , используя Observable Data Holder. Список поддерживаемых типов:

LiveData

Получайте значения из SavedStateHandle , обернутого в наблюдаемый объект LiveData используя getLiveData() . Когда значение ключа обновляется, LiveData получает новое значение. Чаще всего значение устанавливается в результате взаимодействия пользователя, например, при вводе запроса для фильтрации списка данных. Затем это обновленное значение можно использовать для преобразования LiveData .

Котлин

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

Поддерживаемые типы

Данные, хранящиеся в SavedStateHandle , сохраняются и восстанавливаются как Bundle вместе с остальной частью savedInstanceState для активности или фрагмента.

Сохранение классов, не подлежащих отправке

Если класс не реализует интерфейсы Parcelable или Serializable и не может быть изменен для реализации одного из этих интерфейсов, то невозможно напрямую сохранить экземпляр этого класса в SavedStateHandle .

Начиная с версии Lifecycle 2.3.0-alpha03 , SavedStateHandle позволяет сохранять любой объект, предоставляя собственную логику для сохранения и восстановления объекта в виде Bundle с помощью метода setSavedStateProvider() . SavedStateRegistry.SavedStateProvider — это интерфейс, определяющий единственный метод saveState() , который возвращает Bundle содержащий состояние, которое вы хотите сохранить. Когда SavedStateHandle готов сохранить свое состояние, он вызывает saveState() для получения Bundle из SavedStateProvider и сохраняет Bundle для соответствующего ключа.

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

Котлин

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

Чтобы гарантировать сохранность временного файла при завершении и последующем восстановлении процесса активности, TempFileViewModel может использовать SavedStateHandle для сохранения своих данных. Для сохранения данных TempFileViewModel необходимо реализовать интерфейс SavedStateProvider и установить его в качестве поставщика данных для SavedStateHandle ViewModel :

Котлин

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

Чтобы восстановить данные File после возвращения пользователя, получите объект temp_file Bundle из SavedStateHandle . Это тот же Bundle , который предоставляет saveTempFile() и который содержит абсолютный путь. Затем абсолютный путь можно использовать для создания нового экземпляра File .

Котлин

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}
{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}