o estado em um app é qualquer valor que pode mudar ao longo do tempo. Essa é uma definição muito ampla e compreende tudo, desde um banco de dados da Room até uma variável de 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ção 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 você a especificar onde e como armazenar e usar o estado em um app Android.
Loop de atualização da IU e eventos
Em um app Android, o estado é atualizado em resposta aos eventos. Eventos são entradas
geradas fora do nosso app, como o toque do usuário em um botão que chama um
OnClickListener
, um EditText
que chama um afterTextChanged
ou um acelerômetro
que envia um novo valor.
Todos os apps Android têm um loop de atualização de IU principal que tem a seguinte aparência:
- Evento: um evento é gerado pelo usuário ou por outra parte do programa.
- Estado de atualização: um manipulador de eventos muda o estado.
- Estado de exibição: a IU é atualizada para exibir o novo estado.
No Jetpack Compose, o estado e os eventos são separados. Um estado representa um valor mutável, enquanto um evento representa uma notificação de que algo aconteceu.
Ao separar o estado dos eventos, é possível dissociar a exibição do estado da maneira como ele é armazenado e modificado.
Fluxo de dados unidirecional no Jetpack Compose
O Compose foi criado para fluxo de dados unidirecional. Esse é um design em que o estado desce e os eventos sobem.

Ao seguir o fluxo de dados unidirecional, você pode dissociar os elementos que podem ser compostos que exibem o estado na IU das partes do app que o armazenam e mudam.
O loop de atualização da IU para um app usando o fluxo de dados unidirecional é semelhante a este:
- Evento: um evento é gerado por parte da IU e sobe.
- Estado de atualização: um manipulador de eventos pode mudar o estado.
- Estado de exibição: o estado desce e a IU observa o novo estado e o exibe.
Seguir esse padrão ao usar o Jetpack Compose oferece várias vantagens:
- Capacidade de teste: ao dissociar o estado da IU que o exibe, fica mais fácil testar os dois isoladamente.
- Encapsulamento de estado: como o estado só pode ser atualizado em um lugar, é menos provável que você crie estados inconsistentes (ou bugs).
- Consistência da IU: todas as atualizações de estado são refletidas imediatamente na IU pelo uso de titulares de estado observáveis.
Fluxo de dados ViewModel e unidirecional
Ao usar ViewModel
e LiveData
nos Componentes da arquitetura do Android,
você introduz um fluxo de dados unidirecional no app.
Antes de analisar ViewModel
s com o Compose, considere uma Activity
usando
Android Views e fluxo de dados unidirecional que exibe "Hello, ${name}"
e
permite que o usuário insira o nome dele.
O código para essa tela usando um ViewModel
e uma Activity
:
class HelloViewModel : ViewModel() {
// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
// onNameChanged is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onNameChanged(newName: String) {
_name.value = newName
}
}
class HelloActivity : AppCompatActivity() {
val helloViewModel by viewModels<HelloViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// binding represents the activity layout, inflated with ViewBinding
binding.textInput.doAfterTextChanged {
helloViewModel.onNameChanged(it.toString())
}
helloViewModel.name.observe(this) { name ->
binding.helloText.text = "Hello, $name"
}
}
}
Ao usar os Componentes da arquitetura do Android, introduzimos um
design de fluxo de dados unidirecional nessa Activity
.

Activity
usando ViewModel
.Para ver como o fluxo de dados unidirecional funciona no loop de atualização da IU, considere o
loop desta Activity
:
- Evento:
onNameChanged
é chamado pela IU quando a entrada de texto é modificada. - Estado de atualização:
onNameChanged
faz o processamento e define o estado de_name
. - Estado de exibição: os observadores de
name
são chamados, e a IU exibe o novo estado.
ViewModel e Jetpack Compose
Você pode usar LiveData
e ViewModel
no Jetpack Compose para implementar
o fluxo de dados unidirecional, assim como fez em uma Activity
na seção
anterior.
Este é o código para a mesma tela que a HelloActivity
escrita no Jetpack
Compose usando o mesmo HelloViewModel
:
class HelloViewModel : ViewModel() {
// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
// onNameChanged is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onNameChanged(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// by default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.
// name is the _current_ value of [helloViewModel.name]
// with an initial value of ""
val name: String by helloViewModel.name.observeAsState("")
Column {
Text(text = name)
TextField(
value = name,
onValueChange = { helloViewModel.onNameChanged(it) },
label = { Text("Name") }
)
}
}
HelloViewModel
e HelloScreen
seguem o design do fluxo de dados
unidirecional. O estado flui para baixo de HelloViewModel
, e os eventos fluem para cima de
HelloScreen
.
Considere o loop de evento de IU para este elemento que pode ser composto:
- Evento:
onNameChanged
é chamado em resposta à digitação de um caractere pelo usuário. - Estado de atualização:
onNameChanged
faz o processamento e define o estado de_name
. - Estado de exibição: o valor de
name
muda e é observado no Compose emobserveAsState
. Depois,HelloScreen
é executado novamente (ou faz a recomposição) para descrever a IU com base no novo valor dename
.
Para saber mais sobre como usar ViewModel
e LiveData
para criar
um fluxo de dados unidirecional no Android, leia o Guia para a
arquitetura do app.
Elementos que podem ser compostos sem estado
Um elemento que pode ser composto sem estado é aquele que não pode mudar nenhum estado por conta própria. Componentes sem estado são mais fáceis de testar, tendem a ter menos bugs e abrem mais oportunidades para reutilização.
Se o elemento que pode ser composto tiver estado, será possível torná-lo sem estado usando. Isso é feito com a elevação de estado, que é um padrão de programação em que o estado é movido para o autor da chamada, substituindo o estado interno em um elemento que pode ser composto por um parâmetro e eventos.
Para ver um exemplo de elevação de estado, extraia um elemento que pode ser composto sem estado de
HelloScreen
.
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// helloViewModel follows the Lifecycle as the Activity or Fragment that calls this
// composable function. This lifecycle can be modified by callers of HelloScreen.
// name is the _current_ value of [helloViewModel.name]
val name: String by helloViewModel.name.observeAsState("")
HelloInput(name = name, onNameChange = { helloViewModel.onNameChanged(it) })
}
@Composable
fun HelloInput(
name: String, /* state */
onNameChange: (String) -> Unit /* event */
) {
Column {
Text(name)
TextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
HelloInput
tem acesso ao estado como um parâmetro String
imutável, bem como
um evento onNameChange
que pode ela pode chamar quando quiser solicitar a mudança
de estado.
Lambdas são a maneira mais comum de descrever eventos em um elemento que pode ser composto. Aqui,
definimos um evento onNameChange
com um lambda que usa uma String
, aplicando
a sintaxe de tipo de função (String) -> Unit
do Kotlin. Observe que onNameChange
está
no presente, porque o evento não significa que o estado já foi modificado, mas
que o elemento que pode ser composto está solicitando que o gerenciador de eventos o modifique.
HelloScreen
é um elemento que pode ser composto com estado porque tem uma dependência na
classe final HelloViewModel
, que pode mudar diretamente o estado name
.
Não há como o autor da chamada de HelloScreen
controlar atualizações para o
estado name
. HelloInput
é um elemento que pode ser composto sem estado porque não consegue
mudar diretamente nenhum estado.
Ao elevar o estado de HelloInput
, é mais fácil fundamentar o
elemento, reutilizá-lo em situações diferentes e fazer testes. HelloInput
está
dissociada do modo como o estado é armazenado. Isso significa que, se você modificar ou
substituir HelloViewModel
, não precisará mudar a forma como HelloInput
é
implementada.
O processo de elevação de estado permite estender o fluxo de dados unidirecional para elementos que podem ser compostos sem estado. O diagrama de fluxo de dados unidirecional desses elementos mantém o estado para baixo e os eventos para cima à medida que mais elementos interagem com o estado.
É importante entender que um elemento que pode ser composto sem estado ainda pode interagir com um estado que muda ao longo do tempo usando o fluxo de dados unidirecional e a elevação de estado.
Para entender como isso funciona, considere o loop de atualização da IU para HelloInput
:
- Evento:
onNameChange
é chamado em resposta à digitação de um caractere pelo usuário. - Estado de atualização:
HelloInput
não pode modificar diretamente o estado. O autor da chamada pode optar por modificar os estados em resposta ao eventoonNameChange
. Aqui, o autor da chamada,HelloScreen
, chamaráonNameChanged
emHelloViewModel
, o que faz com que o estadoname
seja atualizado. - Estado de exibição: quando o valor de
name
é modificado,HelloScreen
é chamada novamente com oname
atualizado devido aobserveAsState
. Ele chamaráHelloInput
novamente com o novo parâmetroname
. O ato de chamar novamente elementos que podem ser compostos em resposta a mudanças de estado é denominado recomposição.
Composição e recomposição
Uma composição descreve a IU e é produzida pela execução de elementos que podem ser compostos. Uma composição é uma estrutura em árvore dos elementos que podem ser compostos que descrevem sua IU.
Durante a composição inicial, o Jetpack Compose acompanhará os elementos que você chama para descrever sua IU em uma composição. Depois, quando o estado do app mudar, o Jetpack Compose programará a recomposição. A recomposição executa os elementos que podem ser compostos que podem ter mudado em resposta a modificações de estado, e o Jetpack Compose atualiza a composição para refletir as mudanças.
Uma composição só pode ser produzida por uma composição inicial e atualizada por recomposição. A única maneira de modificar uma composição é pela recomposição.
Para saber mais sobre a composição inicial e a recomposição, consulte Trabalhando com o Compose.
Estado dos elementos que podem ser compostos
As funções que podem ser compostas podem armazenar um único objeto na memória usando o
elemento remember
. Um valor calculado por remember
é armazenado durante
a composição inicial, e o valor armazenado é retornado durante a recomposição.
O remember
pode ser usado para armazenar objetos mutáveis e imutáveis.
Usar remember para armazenar valores imutáveis
É possível armazenar valores imutáveis ao armazenar em cache operações da IU de alto custo, como
o cálculo da formatação de texto. O valor memorizado é armazenado na composição
com o elemento que pode ser composto que chamou o
remember
.
@Composable
fun FancyText(text: String) {
// by passing text as a parameter to remember, it will re-run the calculation on
// recomposition if text has changed since the last recomposition
val formattedText = remember(text) { computeTextFormatting(text) }
/*...*/
}

FancyText
com
formattedText
como filho.Usar remember para criar um estado interno em um elemento que pode ser composto
Ao armazenar um objeto mutável usando o remember
, você adiciona o estado a um elemento que pode ser composto.
É possível usar essa abordagem para criar um estado interno para um único elemento que pode ser composto
com estado.
É altamente recomendável que todo estado mutável usado por elementos que podem ser compostos seja observável.
Isso permite que o Compose recomponha automaticamente sempre que o estado mudar.
O Compose vem com um tipo
State<T>
observável integrado, que é diretamente
integrado ao ambiente de execução dele.
Um bom exemplo de estado interno em um elemento que pode ser composto é o movimento de expandir e recolher de um ExpandingCard
quando o usuário clica em um botão.

ExpandedCard
entre os estados recolhido e expandido.Esse elemento que pode ser composto tem um estado importante: expanded
. Quando estiver no estado expanded
,
o elemento mostrará o corpo, mas o ocultará quando estiver recolhido.

ExpandingCard
com
o estado expanded
como filho.É possível adicionar um estado expanded
a um elemento que pode ser composto. Para isso, lembre-se de
mutableStateOf(initialValue)
.
@Composable
fun ExpandingCard(title: String, body: String) {
// expanded is "internal state" for ExpandingCard
var expanded by remember { mutableStateOf(false) }
// describe the card for the current state of expanded
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(text = title)
// content of the card depends on the current value of expanded
if (expanded) {
// TODO: show body & collapse icon
} else {
// TODO: show expand icon
}
}
}
}
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
programará a recomposição de qualquer função que pode ser composta
que leia value
. No caso de ExpandingCard
, sempre que expanded
é mudado,
isso faz com que ExpandingCard
seja recomposto.
Há três maneiras de declarar um objeto MutableState
em um elemento que pode ser composto:
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. É preciso escolher aquela que produz o código mais fácil de ler no elemento que pode ser composto que você está escrevendo.
É possível usar o valor do estado interno em um elemento que pode ser composto como um parâmetro para
outro elemento ou até mesmo mudar quais elementos são chamados. Em
ExpandingCard
, uma declaração "if" mudará o conteúdo do cartão com base no
valor atual de expanded
.
if (expanded) {
// TODO: show body & collapse icon
} else {
// TODO: show expand icon
}
Modificar o estado interno em um elemento que pode ser composto
O estado precisa ser modificado por eventos em um elemento que pode ser composto. Se você modificar o estado ao executar um elemento que pode ser composto em vez de em um evento, esse será um efeito colateral do elemento, o que precisará ser evitado. Para ver mais informações sobre efeitos colaterais no Jetpack Compose, consulte Trabalhando com o Compose.
Para concluir o elemento que pode ser composto ExpandingCard
, exibiremos o body
e um
botão de recolher quando expanded
for true
e um botão de expansão quando expanded
for false
.
@Composable
fun ExpandingCard(title: String, body: String) {
var expanded by remember { mutableStateOf(false) }
// describe the card for the current state of expanded
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(text = title)
// content of the card depends on the current value of expanded
if (expanded) {
Text(text = body, Modifier.padding(top = 8.dp))
// change expanded in response to click events
IconButton(onClick = { expanded = false }, modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandLess)
}
} else {
// change expanded in response to click events
IconButton(onClick = { expanded = true }, modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandMore)
}
}
}
}
}
Nesse elemento que pode ser composto, o estado é modificado em resposta a eventos onClick
. Como
expanded
está usando var
com a sintaxe de
delegação de propriedade (link em inglês),
os callbacks onClick
podem atribuir expanded
diretamente.
IconButton(onClick = { expanded = true }, /* … */) {
// ...
}
Agora, podemos descrever o loop de atualização da IU para ExpandingCard
para ver como o
estado interno é modificado e usado pelo Compose.
- Evento:
onClick
é chamado em resposta ao toque do usuário em um dos botões. - Estado de atualização:
expanded
é mudado no listeneronClick
usando a atribuição. - Estado de exibição:
ExpandingCard
faz a recomposição porqueexpanded
é oState<Boolean>
que foi modificado, eExpandingCard
o lê na linhaif(expanded)
. Em seguida,ExpandingCard
descreve a tela para o novo valor deexpanded
.
Usar outros tipos de estado no Jetpack Compose
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 um State<T>
para que o
Jetpack Compose possa fazer automaticamente a recomposição quando o estado for modificado.
O Compose é enviado com funções para criar State<T>
a partir de tipos observáveis comuns
usados em apps Android:
Você pode criar uma função de extensão para o Jetpack Compose para ler outros tipos
observáveis se o app usar uma classe observável personalizada. Consulte a implementação dos
builtins para ver exemplos de como fazer isso. Qualquer objeto que permita que o Jetpack Compose
faça a inscrição em todas as mudanças pode ser convertido em State<T>
e lido por um
elemento que pode ser composto.
Também é possível criar camadas de integração para objetos de estado não observáveis usando
invalidate
para acionar a recomposição manualmente. Isso é reservado para situações em que
você precisa interoperar com um tipo não observável. Usar invalidate
de forma incorreta é
fácil e tende a levar a um código complexo que é mais difícil de ler do que o
mesmo código usando objetos de estado observáveis.
Separar o estado interno dos elementos de IU que podem ser compostos
O ExpandingCard
na última seção tem o estado interno. Como resultado, o
autor da chamada não pode controlar o estado. Isso significa, por exemplo, que se você quiser
iniciar um ExpandingCard
no estado expandido, não será possível fazer isso. Também
não é possível fazer o cartão se expandir em resposta a outro evento, como o
clique do usuário em um Fab
. Isso também significa que se você quiser mover o estado expanded
para um ViewModel
, isso não será possível.
Por outro lado, usando o estado interno em ExpandingCard
, um autor de chamada que
não precisa controlar ou elevar o estado pode usá-lo sem precisar
gerenciá-lo.
Ao desenvolver elementos que podem ser compostos 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 chamadas que não se importam com ele, e a sem estado é necessária para autores de chamada que precisam controlar ou elevar o estado.
Para fornecer as interfaces com e sem estado, extraia um elemento que pode ser composto sem estado que exiba a IU usando uma elevação de estado.
Observe que os dois elementos são chamados ExpandingCard
, mesmo que tenham
parâmetros diferentes. A convenção de nomenclatura para elementos que podem ser compostos que emitem a IU
é um substantivo com IniciaisMaiúsculas que descreve o que o composto representa na
tela. Nesse caso, ambos representam um ExpandingCard
. Essa convenção de
nomenclatura é aplicada em todas as bibliotecas do Compose, como em
TextField
e TextField
.
Este ExpandingCard
é dividido em elementos que podem ser compostos com e sem estado:
// this stateful composable is only responsible for holding internal state
// and defers the UI to the stateless composable
@Composable
fun ExpandingCard(title: String, body: String) {
var expanded by remember { mutableStateOf(false) }
ExpandingCard(
title = title,
body = body,
expanded = expanded,
onExpand = { expanded = true },
onCollapse = { expanded = false }
)
}
// this stateless composable is responsible for describing the UI based on the state
// passed to it and firing events in response to the buttons being pressed
@Composable
fun ExpandingCard(
title: String,
body: String,
expanded: Boolean,
onExpand: () -> Unit,
onCollapse: () -> Unit
) {
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(title)
if (expanded) {
Spacer(Modifier.height(8.dp))
Text(body)
IconButton(onClick = onCollapse, Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandLess)
}
} else {
IconButton(onClick = onExpand, Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandMore)
}
}
}
}
}
A elevação de estado no Compose é um padrão para mover um estado para o autor da chamada e transformar um elemento que pode ser composto sem estado. 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 que pode ser composto, defina-os usando lambdas da mesma forma que
ExpandingCard
faz com onExpand
e onCollapse
.
O estado elevado dessa maneira tem algumas propriedades importantes:
- Fonte única da verdade: ao mover o estado em vez de duplicá-lo,
garantimos que existe apenas uma fonte de verdade para
expanded
. Isso ajuda a evitar bugs. - Encapsulado: somente o
ExpandingCard
com estado poderá modificar o estado. É totalmente interno. - Compartilhável: o estado elevado pode ser compartilhado com vários elementos que podem ser compostos. Digamos que
queremos ocultar um botão
Fab
quando oCard
for expandido. A elevação poderia nos permitir fazer isso. - Interceptável: os autores de chamadas para
ExpandingCard
sem estado podem decidir ignorar ou modificar eventos antes de mudar o estado. - Desacoplado: o estado de
ExpandingCard
sem estado pode ser armazenado em qualquer lugar. Por exemplo, agora é possível movertitle
,body
eexpanded
para umViewModel
.
A hospedagem feita dessa forma também segue o fluxo de dados unidirecional. O estado é transmitido para baixo do elemento que pode ser composto com estado, e os eventos fluem para cima do elemento sem estado.

ExpandingCard
com
e sem estado.Mudanças internas de estado e configuração
Os valores que são lembrados por remember
em uma composição são esquecidos e
recriados durante mudanças de configuração, como a rotação.
Se você usar o remember { mutableStateOf(false) }
, o ExpandingCard
com estado
será redefinido para ser recolhido sempre que o usuário rotacionar o smartphone. Podemos corrigir isso
usando o estado de instância salvo, para salvar e restaurar automaticamente o estado
quando houver mudanças na configuração.
@Composable
fun ExpandingCard(title: String, body: String) {
var expanded by savedInstanceState { false }
ExpandingCard(
title = title,
body = body,
expanded = expanded,
onExpand = { expanded = true },
onCollapse = { expanded = false }
)
}
A função que pode ser composta
savedInstanceState<T>
retorna um MutableState<T>
que é automaticamente salvo e restaurado em
mudanças de configuração. Use-a para qualquer estado interno que um usuário
esperaria que resistissem às mudanças de configuração.
Saiba mais
Para saber mais sobre o estado e o Jetpack Compose, consulte Como usar estados no codelab do Jetpack Compose.