Según hacia dónde se eleve el estado y la lógica requerida, puedes usar diferentes APIs para almacenar y restablecer el estado de tu IU. Cada app usa una combinación de APIs para lograrlo.
Cualquier app para Android podría perder su estado de IU debido a la actividad o la recreación del proceso. Esta pérdida de estado puede producirse debido a los siguientes eventos:
- Cambios de configuración: La actividad se destruye y se vuelve a crear, a menos que el cambio de configuración se controle de forma manual.
- Cierre del proceso iniciado por el sistema: La app está en segundo plano y el dispositivo libera recursos (como la memoria) para que los usen otros procesos.
La preservación del estado después de estos eventos es esencial para que el usuario tenga una experiencia positiva. La selección del estado que se conservará depende de los flujos de usuarios únicos de tu app. Como práctica recomendada, como mínimo, debes conservar las entradas del usuario y el estado relacionado con la navegación. Algunos ejemplos pueden ser la posición de desplazamiento de una lista, el ID del elemento sobre el cual el usuario desea obtener más detalles, la selección de las preferencias del usuario en curso o la entrada en los campos de texto.
En esta página, se resumen las API disponibles para almacenar el estado de la IU según dónde se eleve el estado y la lógica que lo necesita.
Lógica de la IU
Si el estado se eleva en la IU, ya sea en funciones que admiten composición o en clases de contenedores de estados sin formato con alcance de Composition, puedes usar rememberSaveable
para retener el estado en toda la actividad y el proceso de recreación.
En el siguiente fragmento, se usa rememberSaveable
para almacenar un solo estado de elemento de IU booleano:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails
es una variable booleana que almacena si la burbuja de chat se contrae o se expande.
rememberSaveable
almacena el estado del elemento de la IU en un objeto Bundle
a través del mecanismo de estado de la instancia guardada.
Puede almacenar tipos primitivos automáticamente en el paquete. Si tu estado se mantiene en un tipo que no es primitivo, como una clase de datos, puedes usar diferentes mecanismos de almacenamiento, como el uso de Parcelize
con las API de Compose, como listSaver
y mapSaver
o implementar una clase Saver personalizada que extienda el entorno de ejecución de Compose Saver
. Consulta la documentación sobre formas de almacenar el estado para obtener más información sobre estos métodos.
En el siguiente fragmento, la API de Compose rememberLazyListState
almacena LazyListState
, que consiste en el estado de desplazamiento de un LazyColumn
o LazyRow
, con rememberSaveable
. Usa un objeto LazyListState.Saver
, que es un ahorro personalizado que puede almacenar y restablecer el estado de desplazamiento. Después de una actividad o una recreación del proceso (por ejemplo, después de un cambio de configuración, como cuando se cambia la orientación del dispositivo), se conserva el estado de desplazamiento.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Práctica recomendada
rememberSaveable
usa un objeto Bundle
para almacenar el estado de la IU, que se comparte con otras API que también escriben en él, como las llamadas onSaveInstanceState()
en tu actividad. Sin embargo, el tamaño de este Bundle
es limitado, y el almacenamiento de objetos grandes podría generar excepciones TransactionTooLarge
en el entorno de ejecución. Esto puede ser particularmente problemático en una sola app de Activity
en la que se usa la misma Bundle
en toda la app.
Para evitar este tipo de falla, no debes almacenar objetos complejos grandes ni listas de objetos en el paquete.
En cambio, almacena el estado mínimo requerido, como los ID o las claves, y úsalo para delegar el restablecimiento de estados de IU más complejos a otros mecanismos, como el almacenamiento continuo.
Estas opciones de diseño dependen de los casos prácticos específicos de tu app y cómo se comportan tus usuarios.
Cómo verificar el restablecimiento del estado
Puedes verificar que el estado almacenado con rememberSaveable
en tus elementos de Compose se restablezca correctamente cuando se vuelva a crear la actividad o el proceso. Hay APIs específicas para lograrlo, como StateRestorationTester
. Consulta la documentación para obtener más información.
Lógica empresarial
Si se eleva el estado de tu elemento de IU a ViewModel
porque la lógica empresarial lo requiere, puedes usar las APIs de ViewModel
.
Uno de los principales beneficios de usar ViewModel
en tu aplicación para Android es que controla los cambios de configuración sin costo alguno. Cuando hay un cambio de configuración y la actividad se destruye y se vuelve a crear, el estado de la IU elevado a ViewModel
se conserva en la memoria. Después de la recreación, la instancia ViewModel
anterior se adjunta a la instancia de la actividad nueva.
Sin embargo, una instancia ViewModel
no sobrevive al cierre del proceso iniciado por el sistema.
Para que el estado de la IU sobreviva, usa el módulo de estado guardado para ViewModel, que contiene la API de SavedStateHandle
.
Práctica recomendada
SavedStateHandle
también usa el mecanismo Bundle
para almacenar el estado de la IU, por lo que solo debes usarlo para almacenar el estado de elementos de la IU simple.
El estado de la IU de la pantalla, que se produce con la aplicación de reglas empresariales y el acceso a capas de la aplicación que no sean de la IU, no se deben almacenar en SavedStateHandle
debido a su potencial complejidad y tamaño de Google Analytics. Puedes usar diferentes mecanismos para almacenar datos complejos o grandes, como el almacenamiento local persistente. Después de un proceso de recreación, la pantalla se vuelve a crear con el estado transitorio que se almacenó en SavedStateHandle
(si existe), y el estado de la IU de la pantalla se vuelve a producir desde la capa de datos.
APIs de SavedStateHandle
SavedStateHandle
tiene diferentes APIs para almacenar el estado del elemento de la IU, en particular los siguientes:
Compose State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Compose State
Usa la API de saveable
de SavedStateHandle
para leer y escribir el estado del elemento de la IU como MutableState
, para que sobreviva a la actividad y a la recreación del proceso con la configuración de código mínima.
La API de saveable
admite tipos primitivos listos para usar y recibe un parámetro stateSaver
para usar ahorros personalizados, como rememberSaveable()
.
En el siguiente fragmento, message
almacena los tipos de entrada del usuario en un TextField
:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
Consulta la documentación de SavedStateHandle
para obtener más información sobre el uso de la API de saveable
.
StateFlow
Usa getStateFlow()
para almacenar el estado del elemento de la IU y consumirlo como un flujo desde SavedStateHandle
. El elemento StateFlow
es de solo lectura, y la API requiere que especifiques una clave para que puedas reemplazar el flujo para emitir un valor nuevo. Con la clave que configuraste, puedes recuperar el objeto StateFlow
y recopilar el valor más reciente.
En el siguiente fragmento, savedFilterType
es una variable StateFlow
que almacena un tipo de filtro aplicado a una lista de canales de chat en una app de chat:
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
Cada vez que el usuario selecciona un nuevo tipo de filtro, se llama a setFiltering
. Esto guarda un valor nuevo en SavedStateHandle
almacenado con la clave _CHANNEL_FILTER_SAVED_STATE_KEY_
. savedFilterType
es un flujo que emite el valor más reciente almacenado en la clave. filteredChannels
está suscrito al flujo para realizar el filtrado del canal.
Consulta la documentación de SavedStateHandle
para obtener más información sobre la API de getStateFlow()
.
Resumen
En la siguiente tabla, se resumen las API que se analizan en esta sección y cuándo debes usar cada una para guardar el estado de la IU:
Evento | Lógica de la IU | Lógica empresarial en un objeto ViewModel |
---|---|---|
Cambios de configuración | rememberSaveable |
Automático |
Cierre del proceso iniciado por el sistema | rememberSaveable |
SavedStateHandle |
La API que se debe usar depende de la ubicación del estado y de la lógica que requiere. Para el estado que se usa en la lógica de la IU, usa rememberSaveable
. Para el estado que se usa en la lógica empresarial, si lo mantienes en un ViewModel
, guárdalo con SavedStateHandle
.
Debes usar las APIs de los paquetes (rememberSaveable
y SavedStateHandle
) para almacenar pequeñas cantidades de estado de la IU. Estos datos son el mínimo necesario para restablecer la IU a su estado anterior, junto con otros mecanismos de almacenamiento. Por ejemplo, si almacenas el ID de un perfil que el usuario estaba viendo en el paquete, puedes recuperar datos pesados, como los detalles del perfil, desde la capa de datos.
Si deseas obtener más información sobre las diferentes maneras de guardar el estado de la IU, consulta la documentación general sobre guardado del estado de la IU y la página de capa de datos de la guía de arquitectura.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Dónde elevar el estado
- El estado y Jetpack Compose
- Listas y cuadrículas