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é a variável em uma classe.
Todos os apps Android exibem 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 IU. 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
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
Se você executar esse código, verá que nada acontece. Isso ocorre porque o TextField
não atualiza a si mesmo. Ele é atualizado quando o parâmetro value
muda. Isso se deve
à maneira como a composição e a recombinaçã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
. No caso do ExpandingCard
, sempre que expanded
muda,
o ExpandingCard
é recomposto.
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.h5
)
}
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 Jetpack Compose não exige que você use MutableState<T>
para manter o estado.
Ele é compatível com outros tipos observáveis. Antes de ler outro
tipo observável no Jetpack Compose, você precisa convertê-lo em State<T>
para que o
Jetpack Compose possa fazer automaticamente a recomposição quando o estado for modificado.
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
(link em inglês):collectAsStateWithLifecycle()
O
collectAsStateWithLifecycle()
coleta valores de umFlow
(link em inglês) considerando o ciclo de vida, permitindo que o app não use recursos desnecessários. 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):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.0-beta01")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-beta01"
}
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
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.3.2")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.3.2"
}
-
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
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.3.2")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.3.2"
}
-
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
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.3.2")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.3.2"
}
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
apropriados para o elemento combinável, defina-os usando lambdas da mesma forma que
ExpandingCard
faz com onExpand
e onCollapse
.
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.
- Dissociado: o estado do
ExpandingCard
sem estado pode ser armazenado em qualquer lugar. Por exemplo, agora é possível mover oname
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.h5
)
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 IU 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
Use rememberSaveable
para restaurar o estado da IU após a recriação de uma atividade ou
de um processo. O rememberSaveable
mantém o estado nas recomposições.
Além disso, ele também mantém o estado
nas recriações de atividades e de processos.
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
(link em inglês)
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, R.drawable.myDrawable).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
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
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(inputs = 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
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)