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 ViewModel objetos. 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, ViewModel objetos recebem um SavedStateHandle
objeto 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. Em cenários iniciados pelo sistema, ele é.
Configuração
Para usar 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 combináveis 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
viewModelFactory DSL.
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 fornecida.keys(): retorna todas as chaves contidas noSavedStateHandle.
Além disso, é possível extrair valores de SavedStateHandle usando um detentor de dados observáveis. A lista de tipos com suporte inclui o seguinte:
StateFlow
É possível extrair valores de SavedStateHandle encapsulados em um observável StateFlow. Dependendo se você precisa mutar o valor diretamente, é possível escolher entre um fluxo somente leitura ou mutável:
getStateFlow(): use esse método se precisar apenas 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 fluxo somente leitura e transformá-lo usando operadores de fluxo.getMutableStateFlow(): use esse método se precisar de acesso de leitura e gravação. A atualização do.valuedoMutableStateFlowretornado atualiza automaticamente oSavedStateHandlesubjacente, evitando que você precise definir a chave manualmente.
Na maioria das vezes, esses valores são atualizados 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 complexos da interface, você pode usar o delegado de propriedade saved com a serialização do KotlinX. Esse delegado permite persistir classes de dados @Serializable personalizadas diretamente no SavedStateHandle. Isso preserva o estado do ViewModel durante o encerramento do processo, para que a interface do Compose possa restaurar o estado perfeitamente após a recriação.
Para usar, 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 depender das APIs Saver do Compose em vez da serialização do KotlinX, o artefato lifecycle-viewmodel-compose vai fornecer o delegado
saveable. Isso permite a interoperabilidade entre
SavedStateHandle e Saver do Compose para que qualquer State que você possa
salvar usando rememberSaveable com um Saver personalizado também possa 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
Dados mantidos em um SavedStateHandle são salvos e restaurados como um Bundle,
com o restante do savedInstanceState do 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, considere tornar a
classe parcelável 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 via
a ACTION_IMAGE_CAPTURE intent, 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 de
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 temp_file
Bundle 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
Conteúdo de visualizações
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