MutableState — estado observável e mecanismo de atualização no Compose

Autor: IT Sectr Publicado: 2026-06-28 Tempo de leitura: 7 min

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 — interface compose.runtime com uma única propriedade value (getter + setter)
  • State — interface pai somente leitura, MutableState adiciona capacidade de escrita
  • Recomposição é acionada ao chamar o setter de value dentro de um ciclo snapshot ativo
  • SnapshotMutationPolicy determina quando uma alteração é considerada significativa para recomposição
  • MutableIntState e similares — versões primitivas otimizadas de MutableState

O que é MutableState no Jetpack Compose

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.

Hierarquia de State, MutableState e interfaces derivadas

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.

InterfacePropósitoMétodo de criação
State<T>Contêiner somente leitura
MutableState<T>Contêiner leitura-escritamutableStateOf()
MutableIntStateInt primitivo sem boxingmutableIntStateOf()
MutableFloatStateFloat primitivo sem boxingmutableFloatStateOf()
SnapshotStateListLista observávelmutableStateListOf()
SnapshotStateMapMapa observávelmutableStateMapOf()

SnapshotMutationPolicy: quando acionar a recomposição

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.

kotlin
    // 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)
}

State primitivos: MutableIntState, MutableFloatState, MutableLongState

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.

kotlin
@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")
    }
}

Exemplos práticos de trabalho com MutableState

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.

kotlin
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

Posso usar MutableState sem remember?

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).

Como converter MutableState em uma variável normal?

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.

Qual é mais rápido: mutableStateOf ou mutableIntStateOf?

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.

MutableState pode ser usado como argumento de função Composable?

É 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.

Como criar uma implementação personalizada de MutableState?

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

  • MutableState — interface básica para estado mutável observável no Compose
  • State — versão somente leitura para passar dados sem direito de modificação
  • SnapshotMutationPolicy gerencia as condições para acionar a recomposição ao alterar
  • State primitivos (MutableIntState e outros) eliminam a sobrecarga de boxing
  • SnapshotStateList e SnapshotStateMap rastreiam alterações internas de coleções
  • Recomposição é acionada automaticamente em qualquer chamada ao setter de value
  • Recomendação: use MutableState para possuir o estado e State para passá-lo adiante

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.

Discutir o projeto

Leia também