Módulo Saved State para ViewModel Parte do Android Jetpack.
Conforme mencionado em Como salvar estados de interface, 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.
O estado da interface geralmente é armazenado ou referenciado em objetos ViewModel. Portanto, o uso de
rememberSaveable no Compose 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.
Configuração
Para usar o SavedStateHandle, aceite-o como um argumento de construtor para seu
ViewModel.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Assim, você pode extrair uma instância do ViewModel nos elementos que podem ser compostos
sem qualquer outra configuração. A fábrica ViewModel padrão fornece o
SavedStateHandle apropriado para seu ViewModel.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
Ao fornecer uma instância ViewModelProvider.Factory personalizada, é possível
ativar o uso de SavedStateHandle usando CreationExtras e a
DSL viewModelFactory.
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 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 extrair valores de SavedStateHandle usando um
detentor de dados observáveis. A lista de tipos compatíveis inclui:
StateFlow
É possível extrair valores de SavedStateHandle encapsulados em um observável
StateFlow. Dependendo se você precisa modificar o valor diretamente, é possível
escolher entre um stream somente leitura ou mutável:
getStateFlow(): use essa opção se você só precisar ler o estado. Quando você atualiza o valor da chave em outro lugar noSavedStateHandle, o StateFlow recebe o novo valor. Isso é ideal quando você quer expor um stream somente leitura e transformá-lo usando operadores do Flow.getMutableStateFlow(): use se precisar de acesso de leitura e gravação. Atualizar o.valuedoMutableStateFlowretornado atualiza automaticamente oSavedStateHandle, o que evita que você precise definir a chave manualmente.
Na maioria das vezes, você atualiza esses valores devido a interações do usuário, como a inserção de uma consulta para filtrar uma lista de dados.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
Suporte à serialização do KotlinX
Para estados de IU complexos, use o delegado de propriedade saved com a
serialização do KotlinX. Esse delegado permite que você mantenha classes de dados @Serializable
personalizadas diretamente no SavedStateHandle. Isso preserva o
estado do ViewModel durante o encerramento do processo, para que a IU do Compose possa restaurar
o estado sem problemas após a recriação.
Para usá-lo, anote sua classe de dados com @Serializable e use o delegado saved
no ViewModel:
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
Suporte ao estado do Compose
Se o estado depende das APIs Saver do Compose em vez da serialização
KotlinX, o artefato lifecycle-viewmodel-compose fornece o
delegado saveable. Isso permite a interoperabilidade entre
SavedStateHandle e o Saver do Compose. Todo State que você pode
salvar usando rememberSaveable com um Saver personalizado também pode ser salvo com
SavedStateHandle.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Tipos compatíveis
Os dados mantidos em um SavedStateHandle são salvos e restaurados como um Bundle,
com o restante do savedInstanceState para seu app.
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.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
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:
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 } } }
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.
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 } } }
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.
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
Visualiza conteúdo
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