Módulo de estado guardado para ViewModel (vistas) Parte de Android Jetpack.
Conceptos y la implementación de Jetpack Compose
Como se mencionó en Cómo guardar estados de IU, los objetos ViewModel pueden controlar los cambios de configuración, por lo que no necesitas preocuparte por el estado durante las rotaciones y otros casos. Sin embargo, si necesitas controlar el cierre del proceso que inició el sistema, te recomendamos que uses la API de SavedStateHandle como respaldo.
Por lo general, se almacena o se hace referencia al estado de la IU en objetos ViewModel y no en actividades, por lo que usar onSaveInstanceState() requiere código estándar que el módulo de estado guardado pueda manejar por ti.
Cuando se usa este módulo, los objetos ViewModel reciben un objeto SavedStateHandle a través de su constructor. Este objeto es un mapa de clave-valor que te permite escribir y recuperar objetos en el estado guardado y desde este. Estos valores persisten después de que el sistema finaliza el proceso y quedan disponibles a través del mismo objeto.
El estado guardado está vinculado a la pila de tareas. Si la pila de tareas desaparece, también desaparece el estado guardado. Esto puede ocurrir cuando fuerzas la detención de una app, la quitas del menú Recientes o reinicias el dispositivo. En esos casos, la pila de tareas desaparece y no puedes restablecer la información en estado guardado. En las situaciones de descarte del estado de la IU iniciado por el usuario, no se restablece el estado guardado. En situaciones iniciadas por el sistema, sí se restablece.
Configuración
A partir de Fragment 1.2.0 o su dependencia transitiva Activity 1.1.0, puedes aceptar un SavedStateHandle como argumento de constructor de tu ViewModel.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Luego, podrás recuperar una instancia de ViewModel sin ninguna configuración adicional. La fábrica predeterminada de ViewModel proporciona el SavedStateHandle apropiado para tu 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); ... } ... }
Cuando proporcionas una instancia de ViewModelProvider.Factory personalizada, puedes habilitar el uso de SavedStateHandle extendiendo AbstractSavedStateViewModelFactory.
Cómo trabajar con SavedStateHandle
La clase SavedStateHandle es un par clave-valor que te permite escribir y recuperar datos hacia el estado guardado y desde este a través de los métodos set() y get().
Mediante SavedStateHandle, se retiene el valor de la búsqueda luego del cierre del proceso, lo que asegura que el usuario vea el mismo conjunto de datos filtrados antes y después de la recreación sin necesidad de que la actividad o el fragmento guarden, restablezcan o reenvíen el valor a ViewModel de forma manual.
SavedStateHandle también tiene otros métodos que quizás esperas cuando interactúas con un mapa de clave-valor:
contains(String key): Comprueba si hay un valor para la clave dada.remove(String key): Quita el valor de la clave dada.keys(): Muestra todas las claves incluidas enSavedStateHandle.
Además, puedes recuperar valores de SavedStateHandle con un contenedor de datos observables. Estos son los tipos compatibles:
LiveData
Recupera valores de SavedStateHandle que se unen en un observable de LiveData por medio de getLiveData(). Cuando se actualiza el valor de la clave, LiveData recibe el valor nuevo. Con frecuencia, el valor se establece debido a las interacciones del usuario, como ingresar una búsqueda para filtrar una lista de datos. Este valor actualizado se puede usar 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 admitidos
Los datos conservados en un SavedStateHandle se guardan y se restablecen como un Bundle, junto con el resto de las savedInstanceState correspondientes a la actividad o al fragmento.
Cómo guardar clases no parcelables
Si una clase no implementa Parcelable o Serializable y no se puede modificar para que implemente una de esas interfaces, entonces no es posible guardar directamente una instancia de esa clase en un SavedStateHandle.
Desde Lifecycle 2.3.0-alpha03, SavedStateHandle te permite guardar cualquier objeto, ya que proporciona tu propia lógica para guardar y restablecer el objeto como un Bundle mediante el método setSavedStateProvider().
SavedStateRegistry.SavedStateProvider es una interfaz que define un único método saveState() que devuelve un Bundle que contiene el estado que deseas guardar. Cuando SavedStateHandle está listo para guardar su estado, llama a saveState() para recuperar el Bundle de SavedStateProvider y guarda el Bundle para la clave asociada.
Considera un ejemplo de una app que solicita una imagen de la app de cámara a través del intent ACTION_IMAGE_CAPTURE, pasando un archivo temporal para el lugar donde la cámara debe almacenar la imagen. El TempFileViewModel encapsula la lógica para crear ese archivo temporal.
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; } }
A fin de garantizar que el archivo temporal no se pierda si el proceso de la actividad finaliza y luego se restablece, TempFileViewModel puede usar SavedStateHandle para conservar sus datos. A fin de permitir que TempFileViewModel guarde sus datos, implementa SavedStateProvider y establécelo como un proveedor en el SavedStateHandle de 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 restablecer los datos de File cuando el usuario vuelva, recupera el Bundle del temp_file de SavedStateHandle. Este es el mismo Bundle proporcionado por saveTempFile() que contiene la ruta de acceso absoluta. Luego, se puede usar esta ruta para crear una instancia de un File nuevo.
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; } } }
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Cómo guardar estados de la IU
- Cómo trabajar con objetos de datos observables
- Cómo crear ViewModels con dependencias