Módulo Saved State para ViewModel (Views) Parte do Android Jetpack.
Conceitos e implementação do Jetpack Compose
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, não em
atividades. O uso de onSaveInstanceState() exige o uso de 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
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 noSavedStateHandle.
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); } }
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
fragmento.
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.
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; } } }
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