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ê precisa lidar com encerramentos de processo
iniciados pelo sistema, é recomendável usar a API SavedStateHandle
como backup.
Em geral, o estado da interface é armazenado ou referenciado em objetos ViewModel
, não
em atividades. O uso de onSaveInstanceState()
ou rememberSaveable
exige um 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 acessar objetos de e para o estado salvo. Esses valores
persistem depois que o processo é encerrado pelo sistema e permanecem disponíveis
pelo mesmo objeto.
O estado salvo fica vinculado à pilha de tarefas. Portanto, se ela desaparece, o estado também desaparece. Isso pode ocorrer ao forçar o fechamento ou remover o app do menu "Recentes" ou ao reiniciar o dispositivo. Nesses casos, a pilha de tarefas desaparece e não é possível restaurar as informações do estado salvo. Em cenários em que o estado da interface iniciado pelo usuário é dispensado, o estado salvo não é restaurado. Já em cenários iniciados pelo sistema, o estado é salvo.
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 extrair uma instância do ViewModel
sem qualquer 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
acessar dados de e para o estado salvo, usando os métodos
set()
e get()
.
Usando SavedStateHandle
, o valor da consulta é retido após o encerramento 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 extrair valores de SavedStateHandle
usando um
detentor de dados observáveis. Veja a lista de tipos com suporte:
LiveData
Extraia 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 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); } }
StateFlow
Extraia 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 a versão
saveable
APIs que permitem a interoperabilidade entre o SavedStateHandle
e o Compose
Saver
para que qualquer State
que você
pode salvar via rememberSaveable
com um Saver
personalizado também podem ser salvas com SavedStateHandle
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Tipos com suporte
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 com suporte direto
Por padrão, você pode chamar set()
e get()
em um SavedStateHandle
para os
mesmos tipos de dados que um Bundle
, como mostrado abaixo.
Suporte para tipo/classe | Suporte para 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, parcele-a
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
.
No
Lifecycle 2.3.0-alpha03 e versões mais recentes,
SavedStateHandle
permite que você salve qualquer objeto fornecendo sua
lógica para salvar e restaurar o 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
está pronto para salvar o estado, ele chama saveState()
para extrair 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 pela
intent ACTION_IMAGE_CAPTURE
,
transmitindo um arquivo temporário para onde a câmera vai 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
pode 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, acesse 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; } } }
Usar o SavedStateHandle em testes
Para testar um ViewModel
que usa um SavedStateHandle
como dependência, crie
uma nova instância de SavedStateHandle
com os valores de teste necessários e o transmita
para a instância do ViewModel
que você está testando.
Kotlin
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Outros recursos
Para mais informações sobre o módulo Saved State para ViewModel
, consulte os
recursos a seguir.
Codelabs
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Salvar estados da interface
- Trabalhar com objetos de dados observáveis
- Criar ViewModels com dependências