Сохранение состояния (Saved State) в ViewModel

Это перевод первой части из цикла об интеграции ViewModel c Koltin Coroutines, Data Binding и Navigation.

Чтобы более тесно на практике познакомиться с этими компонентами, записывайтесь на продвинутый курс по разработке приложения «Чат-мессенжер»

Об использовании ViewModel

С момента своего появления ViewModel стала одной из самых «базовых» библиотек Android Jetpack. Основываясь на данных бенчмаркинга для разработчиков за 2019 год, более 40% разработчиков Android добавили ViewModels в свои приложения. Если вы не знакомы с ViewModels, причина этого непонятна: ViewModels продвигают лучшую архитектуру, отделяя данные от вашего пользовательского интерфейса, упрощая управление жизненными циклами пользовательского интерфейса и улучшая тестируемость. Для полного объяснения смотрите ViewModels: простой пример и официальную документацию.

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

  1. Сохранение состояния (Saved State) в ViewModel — Данные ViewModel выживают после перезапуска фонового процесса
  2. NavGraph с ViewModel — интеграция ViewModels и библиотеки навигации
  3. Использование ViewModels в привязке данных (data-binding) — Простое связывание данных с ViewModels и LiveData
  4. viewModelScope — интеграция Kotlin Coroutines и ViewModels

Сохранение состояния (Saved State) в ViewModels

Добавлено в lifecycle-viewmodel-savestate: 1.0.0-alpha01
Для Java и Kotlin

Проблема onSaveInstanceState

Когда ViewModel изначально запускался, возникла запутанная проблема, связанная с onSaveInstanceState. Активити и фрагменты могут быть уничтожены тремя способами:

  1. Вы хотели уйти навсегда: Пользователь уходит или явно закрывает активити — скажем, нажав кнопку «Назад» или запустив некоторый код, вызывающий метод finish(). Активити навсегда исчезла.
  2. Произошло изменение конфигурации: Пользователь поворачивает устройство или вносит другие изменения в конфигурацию. Активити должна быть немедленно восстановлена.
  3. Приложение переходит в фоновый режим, и его процесс останавливается. Это происходит, когда на устройстве недостаточно памяти и ему нужно быстро освободить ее. Когда пользователь вернется к вашему приложению, активити нужно будет восстановить.

В ситуациях 2 и 3 вы хотите перестроить активити. ViewModels всегда помогали вам справиться с ситуацией 2, потому что ViewModel не разрушается при изменении конфигурации; но в ситуации 3 ViewModel также уничтожается, поэтому вам действительно нужно сохранять и восстанавливать данные, используя обратный вызов onSaveInstanceState в своей активити. Более подробно об этом здесь.




Saved State Module

Модуль ViewModel Saved State поможет вам справиться с третьей ситуацией: смерть процесса. ViewModel больше не нужно отправлять и получать информацию о состоянии в и из активити. Вместо этого вы теперь можете обрабатывать сохранение и восстановление данных в ViewModel. ViewModel теперь может действительно обрабатывать и хранить все свои собственные данные.

Это делается с помощью SavedStateHandle, который очень похож на Bundle; это map данных ключ-значение. Этот «пакет» SavedStateHandle находится в ViewModel и переживает смерть фонового процесса. Любые данные, которые вы должны были сохранить ранее в onSaveInstanceState, теперь могут быть сохранены в SavedStateHandle. Например, идентификатор пользователя — это то, что вы можете сохранить в SavedStateHandle.

Настройка Saved State Module

Давайте посмотрим, как использовать новый модуль. Обратите внимание, что приведенный ниже код очень похож на этот код из шага 6 Lifecycles Codelab. Этот код на Java, код ниже на Kotlin.

Шаг 1: Добавьте зависимость

SavedStateHandle в настоящее время находится в альфа-версии (это означает, что API может измениться, и мы собираем отзывы), и это отдельная библиотека. Зависимость для добавления:

implementation ‘androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01’

Обратите внимание: если вы хотите быть в курсе того, какие изменения происходят в библиотеке, загляните на страницу lifecycle release notes.

Шаг 2. Обновите вызов ViewModelProvider

Далее нужно создать тип ViewModel, который имеет SavedStateHandle. В методе onCreate активити или фрагмента обновите ваш вызов ViewModelProvider:

// This ktx requires at least androidx.fragment:fragment-ktx:1.1.0 or 
// androidx.activity:activity-ktx:1.0.0
val viewModel by viewModels { SavedStateVMFactory(this) }
// Or the non-ktx way...
val viewModel = ViewModelProvider(this, SavedStateVMFactory(this))
            .get(MyViewModel::class.java)

Класс, который создает ViewModel, является фабрикой ViewModel, и есть фабрика ViewModel, которая делает ViewModel с SavedStateHandles под названием SavedStateVMFactory. Созданная ViewModel теперь будет иметь SavedStateHandle, связанную с переданным активити / фрагментом.

Примечание. Предстоящая альфа-версия библиотеки Androidx Activity и фрагментов будет выпущена в июле. В этих выпусках (как отмечено здесь) SavedStateVMFactory станет ViewModelProvider.Factory по умолчанию при создании ViewModel в активити или фрагменте. Это означает, что если вы используете новейшие альфа-версии активити или фрагмента Androidx, вам не нужно добавлять зависимость lifecycle-viewmodel-savestate или явно использовать SavedStateVMFactory. Короче говоря, когда это изменение происходит и если вы используете новые альфа-версии, вы можете пропустить шаги 1 и 2 и просто перейти к шагу 3 ниже.

Шаг 3: Используйте SaveStateHandle во ViewModel

Теперь вы можете использовать SavedStateHandle в вашей ViewModel. Вот пример сохранения идентификатора пользователя в SavedStateHandle:

class MyViewModel(state : SavedStateHandle) : ViewModel() {

    // Keep the key as a constant
    companion object {
        private val USER_KEY = "userId"
    }
    
    private val savedStateHandle = state
    
    fun saveCurrentUser(userId: String) {
        // Sets a new value for the object associated to the key.
        savedStateHandle.set(USER_KEY, userId)
    }
    
    fun getCurrentUser(): String {
        // Gets the current value of the user id from the saved state handle
        return savedStateHandle.get(USER_KEY)?: ""
    }
}
  1. Construct: MyViewModel принимает SavedStateHandle в качестве параметра конструктора.
  2. Save: метод saveNewUser показывает пример сохранения данных в SavedStateHandle. Вы сохраняете пару значений ключа USER_KEY, а затем текущий идентификатор пользователя. При обновлении данных в ViewModel они должны быть сохранены в SavedStateHandle.
  3. Retrieve: saveStateHandle.get (USER_KEY) — пример получения текущего значения, сохраненного в SaveStateHandle.

Теперь, если активити уничтожено из-за поворота экрана или из-за того, что ОС убивает ваш процесс, чтобы освободить память, вы можете быть уверены, что SavedStateHandle будет хранить ваши данные.

Обычно вы будете использовать LiveData в вашей ViewModel. Для этого вы можете использовать метод SavedStateHandle.getLiveData (). Вот пример замены getCurrentUser на LiveData, который позволяет наблюдать:

// getLiveData gets MutableLiveData associated with a key. 
// When the value associated with the key updates, the MutableLiveData does as well.
private val _userId : MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)

// Only expose a immutable LiveData
val userId : LiveData<String> = _userId

Чтобы узнать больше, ознакомьтесь с Шагом 6 Lifecycles Codelab  и официальной документацией.

Вопросы о какой-либо из этих функций? Оставьте комментарий! Спасибо за прочтение!

Продолжение здесь

 

Понравилась статья? Поделиться с друзьями:
Добавить комментарий