Dependendo de onde o estado é elevado e da lógica necessária, é possível usar diferentes APIs para armazenar e restaurar o estado da interface. Cada app usa uma combinação de APIs para fazer isso da melhor forma.
Qualquer app Android pode perder o estado da interface devido à recriação da atividade ou do processo. Os eventos abaixo podem causar essa perda:
- Mudanças de configuração. A menos que a mudança de configuração seja processada manualmente, a atividade vai ser destruída e recriada.
- Encerramento do processo iniciado pelo sistema. O app fica em segundo plano, e o dispositivo libera recursos (como memória) para uso por outros processos.
A preservação do estado após esses eventos é essencial para uma experiência positiva do usuário. A seleção do estado a ser preservado depende dos fluxos de usuários únicos do app. Como prática recomendada, preserve pelo menos o estado da entrada e da navegação do usuário. Isso inclui a posição de rolagem de uma lista, o código do item sobre o qual o usuário quer mais detalhes, a seleção em andamento de preferências do usuário ou a entrada em campos de texto.
Esta página resume as APIs disponíveis para armazenar o estado da interface, dependendo de onde ele é elevado e da lógica que precisa dele.
Lógica da interface
Se o estado for elevado na interface, seja em funções combináveis ou em
classes de detentores de estado simples com escopo para a composição, você vai poder usar
rememberSaveable
para reter o estado na recriação de processos e atividades.
No snippet abaixo, o rememberSaveable
é usado para armazenar um único estado
de elemento da interface 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
é uma variável booleana que é armazenada quando o balão de diálogo é recolhido
ou aberto.
O rememberSaveable
armazena o estado do elemento da interface em um Bundle
usando o
mecanismo de estado salvo da instância.
Ele pode armazenar tipos primitivos no pacote automaticamente. Se o estado
for preservado em um tipo não primitivo, como uma classe de dados, vai ser possível usar
diferentes mecanismos de armazenamento, como a anotação Parcelize
,
usando as APIs do Compose, como listSaver
e mapSaver
, ou implementar uma
classe de armazenamento personalizada estendendo a classe Saver
de execução do Compose. Consulte a documentação Formas de
armazenar o estado para saber mais sobre esses métodos.
No snippet abaixo, a API rememberLazyListState
do Compose armazena LazyListState
, que consiste no estado de rolagem de uma
LazyColumn
ou LazyRow
, usando rememberSaveable
. Ele usa um
LazyListState.Saver
, que é um armazenamento personalizado que pode
manter e restaurar o estado de rolagem. Esse estado é preservado após uma recriação de processo ou atividade, por
exemplo, depois de uma mudança de configuração, como modificar a
orientação do dispositivo.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Prática recomendada
O rememberSaveable
usa um Bundle
para armazenar o estado da interface, que é compartilhado por
outras APIs que também gravam nele, como chamadas onSaveInstanceState()
na atividade. No entanto, o tamanho desse Bundle
é limitado, e o armazenamento de objetos
grandes pode levar a exceções TransactionTooLarge
durante a execução. Isso
pode ser especialmente problemático em apps com uma única Activity
em que o mesmo
Bundle
é usado em todo o app.
Para evitar esse tipo de falha, não armazene objetos grandes e complexos ou listas de objetos no pacote.
Em vez disso, armazene o estado mínimo necessário, como IDs ou chaves, e use-o para delegar a restauração do estado da interface mais complexo a outros mecanismos, como o armazenamento permanente.
Essas escolhas de design dependem dos casos de uso específicos do app e de como os usuários esperam que ele se comporte.
Verificar a restauração do estado
É possível verificar se o estado armazenado com rememberSaveable
nos elementos
do Compose é restaurado corretamente quando a atividade ou o processo é
recriado. Há APIs específicas para isso, como
StateRestorationTester
. Confira a documentação sobre Testes para saber mais.
Lógica de negócios
Se o estado do elemento da interface for elevado ao ViewModel
por ser
necessário para a lógica de negócios, use as APIs do ViewModel
.
Um dos principais benefícios de usar um ViewModel
no app Android é
que ele processa as mudanças de configuração sem custos. Quando há uma mudança
de configuração e a atividade é destruída e recriada, o estado da interface elevado ao
ViewModel
é mantido na memória. Após a recriação, a antiga instância de ViewModel
é anexada à nova instância da atividade.
No entanto, uma instância de ViewModel
não sobrevive ao encerramento do processo iniciado pelo sistema.
Para que o estado da interface sobreviva a esse encerramento, use o módulo Saved State para o
ViewModel, que contém a API SavedStateHandle
.
Prática recomendada
O SavedStateHandle
também usa o mecanismo Bundle
para armazenar o estado da interface.
Use-o apenas para armazenar um estado do elemento da interface simples.
O estado da interface da tela, que é produzido com a aplicação de regras de negócios e o acesso
a camadas do app diferentes da interface, não pode ser armazenado em
SavedStateHandle
devido à possível complexidade e tamanho do app. Use
mecanismos diferentes para armazenar dados complexos ou grandes, como o armazenamento
permanente local. Após a recriação de um processo, a tela é recriada com o
estado transitório restaurado que foi armazenado no SavedStateHandle
(se houver), e o
estado da interface da tela é produzido de novo pela camada de dados.
APIs SavedStateHandle
.
O SavedStateHandle
tem diferentes APIs para armazenar o estado do elemento da interface. Estas são as principais:
State do Compose |
saveable() |
---|---|
StateFlow |
getStateFlow() |
State
do Compose
Use a API saveable
do SavedStateHandle
para ler e gravar o estado
do elemento da interface como MutableState
. Assim, ele sobrevive à recriação de atividades e processos com
uma configuração mínima de código.
A API saveable
oferece suporte a tipos primitivos e recebe um
parâmetro stateSaver
para usar armazenamentos personalizados, assim como rememberSaveable()
.
No snippet abaixo, message
armazena os tipos de entrada do usuário em um
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) } ) }
Consulte a documentação do SavedStateHandle
para mais informações sobre
como usar a API saveable
.
StateFlow
Use getStateFlow()
para armazenar o estado do elemento da interface e consumi-lo como um fluxo
do SavedStateHandle
. O StateFlow
(link em inglês) é somente
leitura, e a API exige que você especifique uma chave para substituir o fluxo e
emitir um novo valor. Com a chave configurada, é possível extrair o StateFlow
e coletar o valor mais recente.
No snippet abaixo, savedFilterType
é uma variável de StateFlow
que
armazena um tipo de filtro aplicado a uma lista de canais em um 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 }
Sempre que o usuário seleciona um novo tipo de filtro, setFiltering
é chamado. Isso
salva um novo valor no arquivo SavedStateHandle
armazenado com a chave.
_CHANNEL_FILTER_SAVED_STATE_KEY_
. savedFilterType
é um fluxo que emite o
valor mais recente armazenado na chave. filteredChannels
fica inscrito no fluxo para
realizar a filtragem do canal.
Consulte a documentação SavedStateHandle
para mais informações sobre a
API getStateFlow()
.
Resumo
A tabela abaixo resume as APIs abordadas nesta seção e quando usar cada uma para salvar o estado da interface:
Evento | Lógica da interface | Lógica de negócios em um ViewModel |
---|---|---|
Mudanças de configuração | rememberSaveable |
Automática |
Encerramento do processo iniciado pelo sistema | rememberSaveable |
SavedStateHandle |
A API a ser usada depende de onde o estado é mantido e da lógica que ele
exige. Para o estado usado na lógica da interface, use rememberSaveable
. Para
o estado usado na lógica de negócios, se ele for preservado em um ViewModel
,
salve-o usando SavedStateHandle
.
Use as APIs de pacote (rememberSaveable
e SavedStateHandle
) para
armazenar pequenas quantidades de estado da interface. Esses dados são o mínimo necessário para restaurar
a interface ao estado anterior com outros mecanismos de armazenamento. Por
exemplo, se você armazenar o ID de um perfil que o usuário estava visualizando no pacote,
poderá buscar dados pesados, como detalhes de perfil, da camada de dados.
Para mais informações sobre as diferentes maneiras de salvar o estado da interface, consulte a documentação geral sobre Salvar estados da interface e a página do guia de arquitetura sobre a camada de dados.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Para onde elevar o estado
- Estado e Jetpack Compose
- Listas e grades