Módulo Saved State para ViewModel Parte do Android Jetpack.
Conforme mencionado em
Como salvar estados de IU,
os objetos ViewModel
podem processar
mudanças de configuração, então você não precisa se preocupar com o estado em rotações
ou outros casos. No entanto, se você precisar lidar com a interrupção do processo
iniciada pelo sistema, é recomendável usar
onSaveInstanceState()
como backup.
O estado da IU geralmente é armazenado ou referenciado em objetos ViewModel
, não
em atividades. Portanto, o uso de onSaveInstanceState()
requer algum código boilerplate que
o módulo Saved State pode processar
para você.
Ao usar esse módulo, os objetos ViewModel
recebem um objeto
SavedStateHandle
pelo construtor. Esse objeto é um mapa de chave-valor que permite
gravar e recuperar objetos de e para o estado salvo. Esses valores
persistem depois que o processo é encerrado pelo sistema e permanecem disponíveis
por meio do mesmo objeto.
Configurar
A partir do Fragment 1.2.0
ou da respectiva dependência transitiva
Activity 1.1.0, é possível aceitar
um SavedStateHandle
como argumento de construtor para seu ViewModel
.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Assim, você pode recuperar uma instância do ViewModel
sem nenhuma outra
configuração. A fábrica ViewModel
padrão fornece o
SavedStateHandle
apropriado para seu ViewModel
.
Kotlin
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); ... } ... }
Ao fornecer uma instância
ViewModelProvider.Factory
,
personalizada, é possível ativar o uso de SavedStateHandle
estendendo
AbstractSavedStateViewModelFactory
.
Como trabalhar com SavedStateHandle
A classe SavedStateHandle
é um mapa de chave-valor que permite gravar e
recuperar dados de e para o estado salvo usando os métodos
set()
e get()
.
Usando SavedStateHandle
, o valor da consulta é retido após a interrupção do processo,
garantindo que o usuário veja o mesmo conjunto de dados filtrados antes e depois
da recriação sem que a atividade ou o fragmento precise salvar, restaurar
e encaminhar manualmente esse valor de volta para ViewModel
.
SavedStateHandle
também tem outros métodos que podem ser esperados ao interagir
com um mapa de chave-valor:
contains(String key)
: verifica se há um valor para a chave fornecida.remove(String key)
: remove o valor da chave especificada.keys()
: retorna todas as chaves contidas emSavedStateHandle
.
Além disso, é possível recuperar valores de SavedStateHandle
usando um
detentor de dados observáveis. Veja a lista de tipos com suporte:
LiveData
Recuperar valores de SavedStateHandle
que são encapsulados em um
LiveData
observável usando
getLiveData()
.
Quando o valor da chave é atualizado, o LiveData
recebe o novo valor. Na maioria
das vezes, o valor é definido devido a interações do usuário, como a inserção de uma consulta para
filtrar uma lista de dados. Esse valor atualizado pode ser usado para
transformar LiveData
.
Kotlin
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 UserViewModelJava(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); } }
StateFlow
Recuperar valores de SavedStateHandle
que são encapsulados em um
StateFlow
observável usando
getStateFlow()
.
Quando você atualiza o valor da chave, o StateFlow
recebe o novo valor. Na maioria
das vezes, é possível definir o valor devido a interações do usuário, por exemplo, a inserção de uma
consulta para filtrar uma lista de dados. Em seguida, você pode transformar esse valor atualizado
usando outros operadores do Flow.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: StateFlow<List<String>> = savedStateHandle.getStateFlow<String>("query") .flatMapLatest { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Suporte para o estado experimental do Compose
O artefato lifecycle-viewmodel-compose
fornece as APIs experimentais saveable
que permitem a interoperabilidade entre o SavedStateHandle
e o
Saver
do Compose. Assim, qualquer State
que você pode salvar por meio de rememberSaveable
com um Saver
personalizado também pode ser salvo com SavedStateHandle
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: List<String> by savedStateHandle.saveable { mutableStateOf
>(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData = query } } }
Tipos compatíveis
Dados mantidos em um SavedStateHandle
são salvos e restaurados como um
Bundle
, com o restante do
savedInstanceState
para a atividade ou o fragmento.
Tipos diretamente compatíveis
Por padrão, você pode chamar set()
e get()
em um SavedStateHandle
para os
mesmos tipos de dados que um Bundle
, como mostrado abaixo:
Compatibilidade com tipo/classe | Compatibilidade com a matriz |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
Se a classe não estender um desses itens na lista acima, torne-a
parcelada adicionando a anotação @Parcelize
do Kotlin ou implementando
Parcelable
diretamente.
Salvar classes não comparáveis
Se uma classe não implementar Parcelable
ou Serializable
e não puder ser modificada
para implementar uma dessas interfaces, não será
possível salvar diretamente uma instância dessa classe em um SavedStateHandle
.
A partir do
Lifecycle 2.3.0-alpha03,
SavedStateHandle
permite que você salve qualquer objeto fornecendo sua
lógica para salvar e restaurar seu objeto como um
Bundle
usando o
método
setSavedStateProvider()
. SavedStateRegistry.SavedStateProvider
é uma interface que define um único método
saveState()
que retorna um Bundle
contendo o estado que você quer salvar. Quando
SavedStateHandle
estiver pronto para salvar o estado, chamará saveState()
para recuperar o Bundle
do SavedStateProvider
e salvar o
Bundle
para a chave associada.
Imagine um exemplo de app que solicita uma imagem do app de câmera por meio da
intent ACTION_IMAGE_CAPTURE
,
transmitindo um arquivo temporário para onde a câmera armazenará a
imagem. O TempFileViewModel
encapsula a lógica para criar esse
arquivo temporário.
Kotlin
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; } }
Para garantir que o arquivo temporário não seja perdido se o processo da atividade for encerrado
e depois restaurado, TempFileViewModel
poderá usar SavedStateHandle
para manter os dados. Para permitir que TempFileViewModel
salve os dados, implemente
SavedStateProvider
e defina-o como um provedor no SavedStateHandle
do ViewModel
:
Kotlin
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; } } }
Para restaurar os dados de File
quando o usuário retornar, recupere o Bundle
temp_file
do SavedStateHandle
. Esse é o mesmo Bundle
fornecido pelo
saveTempFile()
que contém o caminho absoluto. O caminho absoluto pode
ser usado para instanciar um novo File
.
Kotlin
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; } } }
Outros recursos
Para mais informações sobre o módulo Saved State para ViewModel
, consulte os
recursos a seguir.