MutableState é uma interface no Jetpack Compose que representa um contêiner para um valor mutável observável. É a base do sistema reativo do Compose: cada vez que o valor de MutableState muda através do setter, o Compose Runtime notifica todos os componentes leitores e dispara a recomposição. De acordo com Google Android Developers, 2026, entender MutableState é essencial para trabalhar corretamente com estado em uma UI declarativa.
Principais pontos
MutableState é uma interface do pacote androidx.compose.runtime que declara uma única propriedade: override var value: T. O getter retorna o valor atual, o setter escreve um novo e notifica o Compose Runtime da alteração. A interface herda de State<T>, onde value é somente leitura. Essa arquitetura de dois níveis permite separar o acesso: um componente que só precisa ler o valor recebe State<T>, enquanto o componente proprietário recebe MutableState<T>.
A implementação padrão de MutableState é a classe interna SnapshotMutableStateImpl, que usa um mecanismo de snapshots para rastrear alterações. Quando o setter de value é chamado, o snapshot atual registra a escrita e marca todos os ObservedScope registrados como inválidos. Esses escopos (normalmente funções Composable) serão recompostos no próximo frame. Todo o processo ocorre de forma síncrona e sem bloqueios graças à arquitetura Lock-free snapshot.
State vs MutableState: State é uma interface somente leitura usada para APIs públicas de componentes. Quando você declara um parâmetro de função Composable como State<Int>, você garante que o componente pode ler mas não alterar o estado. MutableState é usado dentro do componente proprietário. Essa separação é uma das práticas básicas do Compose que previne alterações não autorizadas.
A hierarquia de interfaces de estado no Compose tem vários níveis. No topo está State<T> com valor somente leitura. Abaixo está MutableState<T> com valor de leitura-escrita. Mais abaixo vêm as versões primitivas especializadas: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState e outras, que evitam o boxing de primitivos.
MutableDoubleState e MutableLongState são menos comuns mas também existem. Interfaces de coleções: MutableListState — para rastrear alterações dentro de uma lista, MutableStateMap — para mapas. Cada uma dessas interfaces é otimizada para um cenário específico e estende o MutableState base com métodos adicionais de manipulação de coleções.
SnapshotStateList e SnapshotStateMap são implementações de listas e mapas mutáveis compatíveis com snapshots. Eles permitem rastrear não apenas a substituição de valor, mas alterações internas: adicionar um item a uma lista, remover, modificar um item existente. Para tais estruturas, mutableStateListOf() e mutableStateMapOf() criam as coleções observáveis correspondentes.
| Interface | Propósito | Método de criação |
|---|---|---|
| State<T> | Contêiner somente leitura | — |
| MutableState<T> | Contêiner leitura-escrita | mutableStateOf() |
| MutableIntState | Int primitivo sem boxing | mutableIntStateOf() |
| MutableFloatState | Float primitivo sem boxing | mutableFloatStateOf() |
| SnapshotStateList | Lista observável | mutableStateListOf() |
| SnapshotStateMap | Mapa observável | mutableStateMapOf() |
SnapshotMutationPolicy é uma interface que determina quando uma alteração no MutableState é considerada significativa. mutableStateOf aceita policy como segundo argumento. Implementações padrão: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (sempre considera a alteração). É possível implementar uma política personalizada para lógica própria.
structuralEquality() — comportamento padrão. Compose compara o novo valor com o antigo usando equals(). Se o resultado for true, a recomposição NÃO é acionada. Isso é conveniente para primitivos e data classes, onde duas instâncias com os mesmos campos são consideradas iguais. Problema: se uma data class contém uma List, equals() faz uma comparação profunda, que pode ser custosa para listas grandes.
referentialEquality() — compara referências usando ===. A recomposição é acionada apenas quando um objeto diferente é atribuído, mesmo que o conteúdo seja idêntico. Isso é ideal para data classes imutáveis onde cada nova instância garante uma alteração. neverEqualPolicy() — sempre considera a alteração significativa sem realizar comparação. Útil quando o setter é chamado raramente e não há necessidade de gastar tempo com equals.
// Policy comparison in practice
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: recomposition ONLY if data changed
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: recomposition on ANY assignment
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() with same fields does NOT trigger recomposition
// user2: even user2.copy() == user2 triggers recomposition (new ref)
}
MutableIntState primitivo e similares são interfaces especializadas que armazenam primitivos sem boxing. Um MutableState<Int> normal armazena Int como Integer, que cria um objeto no heap a cada escrita. MutableIntState armazena int (primitivo), eliminando completamente a sobrecarga de boxing. Isso é especialmente importante para atualizações de alta frequência — contadores, posições de scroll, valores de animação.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — funções que criam MutableState primitivos. As interfaces são chamadas MutableIntState, MutableFloatState, MutableLongState. Elas estendem MutableState<Int>, MutableState<Float> e MutableState<Long> respectivamente, adicionando a propriedade intValue para acesso rápido ao primitivo. Sua implementação interna usa AtomicInteger para leitura/escrita sem bloqueios.
Uso: contadores (Int), posições de scroll (Float offset), marcas de tempo (Long). Na maioria dos cenários cotidianos a diferença de desempenho é imperceptível, mas em LazyList com milhares de itens e animações de transição, os State primitivos fornecem um ganho notável. O Google recomenda usar State primitivos para cenários típicos em vez do mutableStateOf universal.
@Composable
fun ScrollCounter() {
// Bad: boxing on every update
var badCount by remember { mutableStateOf(0) }
// Good: no boxing, primitive storage
var goodCount by remember { mutableIntStateOf(0) }
// Usage is identical
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Considere um componente TodoList, onde MutableState é usado em duas formas: como variáveis separadas para o estado de entrada e como SnapshotStateList para uma lista dinâmica de tarefas. Ambos usam delegação para brevidade do código.
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)
@Composable
fun TodoScreen() {
var inputText by remember { mutableStateOf("") }
val items = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = inputText,
onValueChange = { inputText = it }
)
Button(onClick = {
if (inputText.isNotBlank()) {
items.add(TodoItem(items.size, inputText))
inputText = ""
}
}) { Text("Add") }
}
LazyColumn {
items(items) { item ->
Row(modifier = Modifier.fillMaxWidth().clickable {
val idx = items.indexOf(item)
items[idx] = item.copy(isDone = !item.isDone)
}) {
Checkbox(checked = item.isDone, onCheckedChange = null)
Text(item.text)
}
}
}
}
}
mutableStateListOf cria um SnapshotStateList — uma lista mutável que rastreia alterações em elementos individuais. Quando items.add() e items[n] = newValue são chamados, o Compose vê a mutação e recompoe apenas os elementos da LazyColumn que mudaram. inputText é um MutableState<String> normal. A combinação de dois tipos de MutableState (individual e coleção) é um padrão típico para telas com formulários e listas.
Perguntas frequentes
MutableState sem remember será criado novamente a cada recomposição. Cada nova chamada a mutableStateOf cria um novo objeto e o valor antigo é perdido. Sempre use remember para preservar o State entre recomposições, a menos que o State seja criado fora de um Composable (por exemplo, em um ViewModel).
Leia .value uma vez fora de um snapshot via snapshot { }. Mas isso desativa a reatividade — alterações não desencadearão mais a recomposição. Para leitura única sem assinatura, use currentValue() dentro de um snapshot sem leitura.
mutableIntStateOf é mais rápido porque não requer boxing de int para Integer. Com milhares de atualizações por segundo (animação, scroll), a diferença pode chegar a 30-50% no tempo de alocação. Para atualizações raras (cliques, entrada de texto), a diferença é insignificante.
É possível mas não recomendado. Em vez de MutableState, passe State (somente leitura) + uma lambda onValueChange. Isso implementa o padrão State Hoisting e torna o componente reutilizável. Componentes que aceitam MutableState violam o fluxo de dados unidirecional.
Implemente a interface MutableState e forneça override var value com um getter e setter. No setter você pode adicionar validação ou registro. Para compatibilidade reversa com o Compose Runtime, envolva sua implementação personalizada em snapshotFlow ou use snapshotIncrement.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também