O estado em um app é qualquer valor que pode mudar ao longo do tempo. Essa é uma definição muito ampla e abrange tudo, de um banco de dados do Room até uma variável em uma classe.
Todos os apps Android mostram o estado para o usuário. Alguns exemplos de estado em apps Android:
- Um snackbar que mostra quando não é possível estabelecer uma conexão de rede.
- Uma postagem de blog e comentários associados.
- Animações de ripple em botões que são reproduzidas quando um usuário clica neles.
- Adesivos que podem ser desenhados sobre uma imagem.
O Jetpack Compose ajuda a deixar claro onde e como você armazena e usa o estado em um app Android. Este guia se concentra na conexão entre estado e elementos combináveis, assim como nas APIs que o Jetpack Compose oferece para trabalhar mais facilmente com o estado.
Estado e composição
O Compose é declarativo e, portanto, a única maneira de atualizá-lo é chamando
com novos argumentos o mesmo elemento combinável. Esses argumentos são representações do
estado da interface. Sempre que um estado é atualizado, ocorre uma recomposição. Por isso,
itens como TextField
não são atualizados automaticamente como seriam em
visualizações imperativas baseadas em XML. Um elemento combinável precisa ser explicitamente informado sobre o novo estado
para que seja atualizado corretamente.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
Se você executar esse código e tentar inserir texto, vai notar que nada acontece. Isso ocorre
porque o TextField
não se atualiza. Ele é atualizado quando o parâmetro
value
muda. Isso se deve
à maneira como a composição e a recomposição funcionam no
Compose.
Para saber mais sobre a composição inicial e a recomposição, consulte Trabalhando com o Compose.
Estado em elementos combináveis
As funções combináveis podem usar a
API remember
para armazenar um objeto na memória. Um valor calculado pela remember
é
armazenado durante
a composição inicial e retornado durante a recomposição.
A API remember
pode ser usada para armazenar tanto objetos mutáveis quanto imutáveis.
A função mutableStateOf
cria um
MutableState<T>
observável, que é integrado ao ambiente de execução do Compose.
interface MutableState<T> : State<T> {
override var value: T
}
Qualquer mudança em value
agenda a recomposição de todas as funções combináveis
que leem value
.
Há três maneiras de declarar um objeto MutableState
em um elemento combinável:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
Essas declarações são equivalentes e são fornecidas como açúcar de sintaxe para diferentes usos do estado. Escolha aquela que produz o código mais fácil de ler no combinável que você está criando.
A sintaxe by
delegada requer estas importações:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
É possível usar o valor salvo como parâmetro para outros elementos combináveis ou mesmo como
lógica em instruções para mudar quais desses elementos serão mostrados. Por exemplo, se
você não quiser exibir a saudação quando o nome estiver vazio, use o estado em uma instrução
if
:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
Embora remember
ajude a manter o estado em recomposições, o estado não é
mantido em todas as mudanças de configuração. Para isso, use
rememberSaveable
. O rememberSaveable
salva automaticamente qualquer valor que possa ser
salvo em um Bundle
. Para outros valores, é possível transmitir um objeto de armazenamento personalizado.
Outros tipos de estado com suporte
O Compose não exige que você use MutableState<T>
para manter o estado. Ele
oferece suporte a outros tipos observáveis. Antes de ler outro tipo observável no
Compose, é necessário convertê-lo em um State<T>
para que os elementos combináveis possam
ser recompostos automaticamente quando o estado mudar.
O Compose tem funções integradas para criar State<T>
com base em tipos observáveis comuns
usados em apps Android. Antes de usar essas integrações, adicione os
artefatos adequados, conforme descrito abaixo:
Flow
:collectAsStateWithLifecycle()
O
collectAsStateWithLifecycle()
coleta valores de umFlow
(link em inglês) considerando o ciclo de vida, permitindo que o app conserve recursos. Ele representa o valor emitido mais recentemente pelo ComposeState
. Use essa API como a maneira recomendada de coletar fluxos em apps Android.A seguinte dependência é necessária no arquivo
build.gradle
(precisa ser 2.6.0-beta01 ou mais recente):
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
Flow
(link em inglês):collectAsState()
O
collectAsState
é semelhante aocollectAsStateWithLifecycle
porque também coleta valores de umFlow
e o transforma em umState
do Compose.Use o
collectAsState
para o código independente de plataforma em vez decollectAsStateWithLifecycle
, que é exclusivo para o Android.Outras dependências não são necessárias para
collectAsState
porque ele está disponível emcompose-runtime
.-
O
observeAsState()
começa a observar esteLiveData
e representa os valores dele com umState
.A dependência abaixo é necessária no arquivo
build.gradle
:
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.8")
}
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.8"
}
-
subscribeAsState()
são funções de extensão que transformam os streams reativos do RxJava2, por exemplo,Single
,Observable
eCompletable
(links em inglês), em umState
do Compose.A dependência abaixo é necessária no arquivo
build.gradle
:
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.8")
}
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.8"
}
-
subscribeAsState()
são funções de extensão que transformam os streams reativos do RxJava3, por exemplo,Single
,Observable
eCompletable
(links em inglês), em umState
do Compose.A dependência abaixo é necessária no arquivo
build.gradle
:
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.8")
}
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.8"
}
Com estado X sem estado
Um elemento combinável que usa o remember
para armazenar um objeto cria um estado interno,
transformando o elemento em com estado. O HelloContent
é um exemplo de elemento
com estado porque mantém e modifica internamente o estado de name
. Isso pode
ser útil em situações em que um autor de chamada não precisa controlar o estado e pode
usá-lo sem ter que gerenciar o estado por conta própria. No entanto, os elementos que têm
estado interno tendem a ser menos reutilizáveis e mais difíceis de testar.
Um elemento combinável sem estado é aquele que não tem estado algum. Uma maneira fácil de ficar sem estado é usar a elevação de estado.
Ao desenvolver elementos combináveis reutilizáveis, frequentemente você quer expor uma versão com estado e uma sem estado do mesmo elemento. A versão com estado é conveniente para autores de chamada que não se importam com ele, e a sem estado é necessária para autores de chamada que precisam controlar ou elevar o estado.
Elevação de estado
A elevação de estado no Compose é um padrão para que o autor da chamada possa transformar e remover o estado de um combinável. O padrão geral para elevação de estado no Jetpack Compose é substituir a variável por dois parâmetros:
value: T
: o valor atual a ser exibido.onValueChange: (T) -> Unit
: um evento que solicita a mudança do valor, em queT
é o novo valor proposto.
No entanto, você não se limita a onValueChange
. Se eventos mais específicos forem
adequados para o elemento combinável, defina-os usando lambdas.
O estado elevado dessa maneira tem algumas propriedades importantes:
- Única fonte da verdade: ao mover o estado em vez de duplicá-lo, garantimos que exista apenas uma fonte de verdade. Isso ajuda a evitar bugs.
- Encapsulado: somente elementos combináveis com estado poderão modificar esse estado. Ele é totalmente interno.
- Compartilhável: o estado elevado pode ser compartilhado com vários elementos combináveis. Se você
quiser ler
name
em um combinável diferente, a elevação permitirá fazer isso. - Interceptável: os autores de chamada para elementos combináveis sem estado podem decidir ignorar ou modificar eventos antes de mudar o estado.
- Desacoplado:o estado dos elementos combináveis sem estado pode ser armazenado
em qualquer lugar. Por exemplo, agora é possível mover
name
para umViewModel
.
No exemplo, name
e onValueChange
são extraídos de
HelloContent
e movidos para cima na árvore até um elemento HelloScreen
combinável
que chama o HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
Ao elevar o estado do HelloContent
, é mais fácil entender, reutilizar em situações diferentes e testar o
combinável. O HelloContent
está
dissociado do modo como o estado é armazenado. Isso significa que, se você modifica ou
substitui HelloScreen
, não precisa mudar a forma como HelloContent
é
implementado.

O padrão em que o estado desce e os eventos sobem é chamado de
fluxo de dados unidirecional. Nesse caso, o estado desce de HelloScreen
para HelloContent
e os eventos sobem de HelloContent
para HelloScreen
. Ao
seguir o fluxo de dados unidirecional, você pode dissociar os elementos que exibem
o estado na interface das partes do app que armazenam e mudam o estado.
Consulte a página Onde elevar o estado para saber mais.
Como restaurar o estado no Compose
A API rememberSaveable
se comporta de maneira semelhante a remember
porque
ela mantém o estado nas recomposições e também na recriação de atividades ou
processos usando o mecanismo de estado da instância salvo. Por exemplo, isso acontece
quando a tela é girada.
Formas de armazenar o estado
Todos os tipos de dados adicionados ao Bundle
são salvos automaticamente. Caso você
queira salvar algo que não possa ser adicionado ao Bundle
, há várias
opções.
Parcelize
A solução mais simples é adicionar a anotação
@Parcelize
ao objeto. O objeto se tornará parcelable e poderá ser empacotado. Por
exemplo, esse código cria um tipo de dado parcelable City
e o salva no
estado.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
Se, por algum motivo, @Parcelize
não for adequado, use mapSaver
para
definir sua própria regra de conversão de um objeto em um conjunto de valores que o
sistema pode salvar no Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
Para evitar a necessidade de definir as chaves do mapa, você também pode usar listSaver
e seus índices como chaves:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
Detentores de estado no Compose
A elevação de estado simples pode ser gerenciada nas próprias funções combináveis. No entanto, caso a quantidade de estados a serem gerenciados aumente ou surja uma lógica para realizar em funções combináveis, é recomendável delegar as responsabilidades de lógica e estado a outras classes: detentores de estado.
Consulte a documentação sobre elevação de estado no Compose ou, de forma mais geral, a página Detentores de estado e estado da interface no guia de arquitetura para saber mais.
Reativar cálculos de recuperação quando as chaves mudarem
A API remember
é frequentemente usada com MutableState
:
var name by remember { mutableStateOf("") }
Aqui, o uso da função remember
faz com que o valor MutableState
sobreviva
a recomposições.
Em geral, remember
usa um parâmetro lambda calculation
. Quando remember
é executado pela primeira vez, ele invoca o lambda calculation
e armazena o resultado. Durante
a recomposição, remember
retorna o valor armazenado pela última vez.
Além do estado de armazenamento em cache, também é possível usar remember
para armazenar qualquer objeto ou
resultado de uma operação na composição que é cara para
inicializar ou calcular. Talvez você não queira repetir esse cálculo a cada recomposição.
Um exemplo é a criação deste objeto ShaderBrush
, que é uma operação
cara:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
O remember
armazena o valor até sair da composição. No entanto, há uma
maneira de invalidar o valor armazenado em cache. A API remember
também usa um parâmetro key
ou
keys
. Se qualquer uma dessas chaves mudar, na próxima recomposição
da função, remember
vai invalidar o cache e executar
o bloco lambda de cálculo novamente. Esse mecanismo oferece controle sobre a vida útil de um
objeto na composição. O cálculo permanece válido até que as entradas
mudem, e não até que o valor lembrado saia da composição.
Os exemplos a seguir mostram como esse mecanismo funciona.
Neste snippet, um ShaderBrush
é criado e usado como a pintura em segundo plano
de um elemento combinável Box
. remember
armazena a instância ShaderBrush
porque a recriação dela é cara, conforme explicado anteriormente. remember
usa
avatarRes
como o parâmetro key1
, que é a imagem de plano de fundo selecionada. Se
avatarRes
muda, o pincel é recomposto com a nova imagem e se aplica de novo ao
Box
. Isso pode ocorrer quando o usuário seleciona outra imagem para ser o
segundo plano de um seletor.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
No próximo snippet, o estado é elevado para uma classe detentora de estado simples
MyAppState
. Ele expõe uma função rememberMyAppState
para inicializar uma
instância da classe usando remember
. A exposição dessas funções para criar uma
instância que resiste a recomposições é um padrão comum no Compose. A
função rememberMyAppState
recebe windowSizeClass
, que serve como
o parâmetro key
para remember
. Se esse parâmetro mudar, o app vai precisar
recriar a classe de detentor de estado simples com o valor mais recente. Isso pode ocorrer se,
por exemplo, o usuário gira o dispositivo.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
O Compose usa a implementação de equalização da classe para decidir se uma chave mudou e invalida o valor armazenado.
Armazenar o estado com chaves além da recomposição
A API rememberSaveable
é um wrapper em torno de remember
que pode armazenar
dados em um Bundle
. Essa API permite que o estado sobreviva não apenas
à recomposição, mas também à recriação de atividades e à interrupção do processo iniciada pelo sistema.
rememberSaveable
recebe parâmetros input
para a mesma finalidade que
remember
recebe keys
. O cache é invalidado quando qualquer uma das entradas
muda. Na próxima vez que a função for recomposta, o rememberSaveable
vai executar
o bloco lambda de cálculo de novo.
No exemplo a seguir, rememberSaveable
armazena userTypedQuery
até que
typedQuery
mude:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Saiba mais
Para saber mais sobre o estado e Jetpack Compose, consulte os recursos abaixo.
Exemplos
Jetnews is a sample news reading app, built with Jetpack Compose. The goal of the sample is to showcase the current UI capabilities of Compose.
To try out this sample app, use the latest stable version of Android Studio. You can clone this repository Jetchat is a sample chat app built with Jetpack Compose.
To try out this sample app, use the latest stable version of Android Studio. You can clone this repository or import the project from Android Studio following the steps here.
This sample Learn how this app was designed and built in the design case study, architecture learning journey and modularization learning journey.
This is the repository for the Now in Android app. It is a work in progress 🚧.
Now in Android is a fully functionalJetnews sample
Jetchat sample
Now in Android App
Codelabs
Vídeos
- Um estado de espírito do Compose (vídeo em inglês)
Blogs
- Gerenciamento de estado eficaz para
TextField
no Compose (link em inglês)