O que é State em Kotlin?

Resposta rápida: no Jetpack Compose, State<T> é um valor observável pela interface. Quando um Composable lê state.value e esse valor muda, o Compose agenda a recomposição do trecho dependente. Para estado local, use remember { mutableStateOf(...) } (ou mutableIntStateOf para primitivos); para estado simples que deve ser restaurado após recriação da Activity, use rememberSaveable; para converter Flow/I/O em State na UI, use produceState; para estado de tela e lógica de negócio, exponha StateFlow no ViewModel e colete com collectAsStateWithLifecycle() no Android.

@Composable
fun Contador() {
    var contagem by remember { mutableIntStateOf(0) }

    Button(onClick = { contagem++ }) {
        Text("Cliques: $contagem")
    }
}

O exemplo combina três peças:

  • mutableIntStateOf(0) cria um estado primitivo de Int sem boxing;
  • remember mantém a mesma instância durante recomposições;
  • by usa delegação de propriedade para ler e alterar o valor sem escrever .intValue.

O Compose não redesenha necessariamente a tela inteira. Ele registra onde o estado foi lido e pode ignorar partes que não dependem daquele valor.

State, MutableState e estado comum

Uma variável Kotlin comum não notifica o Compose:

@Composable
fun ContadorQuebrado() {
    var contagem = 0

    Button(onClick = { contagem++ }) {
        Text("Cliques: $contagem")
    }
}

O clique altera a variável, mas não há um estado observável que invalide a composição. Além disso, a variável é recriada quando a função recompõe.

Com MutableState<T>, a escrita pode acionar recomposição:

val contagem = remember { mutableIntStateOf(0) }

Button(onClick = { contagem.intValue++ }) {
    Text("Cliques: ${contagem.intValue}")
}

State<T> oferece apenas leitura (value). MutableState<T> acrescenta escrita. Em componentes reutilizáveis, prefira receber o valor e eventos em vez de entregar o estado mutável diretamente.

mutableIntStateOf e estados primitivos

Para Int, Long, Float e Double, o Compose oferece holders especializados que evitam boxing em cada leitura/escrita:

TipoAPI recomendada
IntmutableIntStateOf
LongmutableLongStateOf
FloatmutableFloatStateOf
DoublemutableDoubleStateOf
Outros / objetosmutableStateOf
@Composable
fun ProgressoLocal() {
    var progresso by remember { mutableFloatStateOf(0f) }
    var passos by remember { mutableIntStateOf(0) }

    Slider(
        value = progresso,
        onValueChange = {
            progresso = it
            passos++
        }
    )
    Text("Passos: $passos")
}

Com by, o delegate já expõe o primitivo (Int, Float, …). Sem by, use .intValue, .floatValue e equivalentes. Continue usando mutableStateOf para String, data classes, sealed hierarchies e qualquer tipo que não seja um desses quatro primitivos.

remember vs rememberSaveable vs ViewModel

NecessidadeOpção comum
Sobreviver apenas a recomposiçõesremember
Restaurar estado simples de UI após recriaçãorememberSaveable
Estado de uma tela com lógica de apresentaçãoViewModel + StateFlow
Restaurar dados após morte do processoSavedStateHandle ou persistência
@Composable
fun Formulario() {
    var filtroTemporario by remember { mutableStateOf("") }
    var nomeDigitado by rememberSaveable { mutableStateOf("") }

    Column {
        TextField(
            value = filtroTemporario,
            onValueChange = { filtroTemporario = it },
            label = { Text("Filtro temporário") }
        )
        TextField(
            value = nomeDigitado,
            onValueChange = { nomeDigitado = it },
            label = { Text("Nome") }
        )
    }
}

rememberSaveable não transforma qualquer objeto em persistente. Tipos aceitos pelo Bundle funcionam diretamente; tipos próprios podem precisar de Saver. Também não use essa API para listas grandes ou dados que pertencem ao banco, à rede ou ao DataStore.

State hoisting: estado desce, eventos sobem

State hoisting (elevação de estado) move o estado para o chamador. O componente recebe o valor atual e um callback para comunicar mudanças:

@Composable
fun CampoDeNome(
    nome: String,
    onNomeChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    OutlinedTextField(
        value = nome,
        onValueChange = onNomeChange,
        label = { Text("Nome") },
        modifier = modifier.fillMaxWidth()
    )
}

@Composable
fun Cadastro() {
    var nome by rememberSaveable { mutableStateOf("") }

    CampoDeNome(
        nome = nome,
        onNomeChange = { nome = it }
    )
}

A dupla value + onValueChange é uma convenção importante no Compose. O filho fica stateless, reutilizável e fácil de testar; o pai vira a fonte única da verdade.

Eleve o estado pelo menos até o ancestral comum mais próximo de todos os componentes que precisam lê-lo ou alterá-lo. Não é necessário mover todo estado local para um ViewModel: abertura de menu, expansão de item e foco temporário podem continuar próximos da UI.

derivedStateOf: estado calculado

derivedStateOf é útil quando uma entrada muda com mais frequência do que a UI precisa atualizar. Um caso típico é mostrar um botão depois que a lista sai do primeiro item:

@Composable
fun ListaDeMensagens(mensagens: List<String>) {
    val listState = rememberLazyListState()
    val mostrarVoltarAoTopo by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 0 }
    }

    Box {
        LazyColumn(state = listState) {
            items(mensagens) { mensagem ->
                Text(mensagem)
            }
        }

        if (mostrarVoltarAoTopo) {
            FloatingActionButton(onClick = { /* iniciar scroll em coroutine */ }) {
                Text("↑")
            }
        }
    }
}

O índice e o deslocamento da lista podem mudar muitas vezes durante o scroll, mas o booleano só alterna quando a condição muda. Não use derivedStateOf para toda concatenação ou cálculo barato: ele também tem custo e não é uma forma genérica de “memorizar” valores.

Estado de tela com ViewModel e StateFlow

Para telas reais, modele carregamento, sucesso e erro como um estado imutável. O ViewModel mantém a versão mutável privada e expõe somente leitura:

data class ListaUiState(
    val carregando: Boolean = false,
    val itens: List<String> = emptyList(),
    val erro: String? = null
)

class ListaViewModel(
    private val repositorio: ItensRepositorio
) : ViewModel() {
    private val _uiState = MutableStateFlow(ListaUiState())
    val uiState: StateFlow<ListaUiState> = _uiState.asStateFlow()

    fun carregar() {
        viewModelScope.launch {
            _uiState.update { it.copy(carregando = true, erro = null) }

            runCatching { repositorio.buscarItens() }
                .onSuccess { itens ->
                    _uiState.value = ListaUiState(itens = itens)
                }
                .onFailure { causa ->
                    _uiState.value = ListaUiState(
                        erro = causa.message ?: "Não foi possível carregar"
                    )
                }
        }
    }
}

Na tela Android com Compose, colete respeitando o ciclo de vida:

@Composable
fun ListaScreen(viewModel: ListaViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when {
        uiState.carregando -> CircularProgressIndicator()
        uiState.erro != null -> Text(uiState.erro!!)
        else -> LazyColumn {
            items(uiState.itens) { item -> Text(item) }
        }
    }
}

collectAsStateWithLifecycle() é a escolha recomendada para Compose no Android porque interrompe e retoma a coleta conforme o Lifecycle. Em Compose Multiplatform ou código sem Lifecycle Android, use collectAsState(). Para aprofundar a arquitetura, consulte o guia de MVVM com Kotlin e o guia de Kotlin Flow.

State, StateFlow, SharedFlow e LiveData

TipoCaracterísticaUso típico
State<T>Observável pelo runtime do ComposeValor lido pela UI Compose
MutableState<T>State com escritaEstado local do Composable
StateFlow<T>Hot flow com valor atualEstado de tela no ViewModel
SharedFlow<T>Hot flow configurável, sem exigir valor inicialDistribuição de eventos ou valores compartilhados
LiveData<T>Observável integrado ao Lifecycle AndroidBases de código Android existentes

StateFlow representa bem estado porque sempre possui um valor atual. Para navegação, snackbar e outros efeitos pontuais, não copie automaticamente o mesmo modelo: eventos exigem pensar em entrega, repetição e perda quando não há coletor.

Coleções e estabilidade do estado

Este código pode não recompor como esperado porque a referência da lista continua a mesma:

val itens = remember { mutableStateOf(mutableListOf("Kotlin")) }
itens.value.add("Compose") // mutação interna; não houve nova atribuição

Prefira estado imutável e atribua uma nova coleção:

var itens by remember { mutableStateOf(listOf("Kotlin")) }
itens = itens + "Compose"

Quando precisa de uma coleção mutável observável pelo Compose, existe mutableStateListOf, mas ainda vale evitar expor mutabilidade sem controle. Leia também o verbete sobre imutabilidade.

produceState: State a partir de trabalho assíncrono

produceState cria um State alimentado por uma coroutine dentro do Composable. É o caminho curto quando a UI precisa observar um Flow, callback ou I/O sem subir imediatamente para um ViewModel:

@Composable
fun PerfilRemoto(userId: String, repositorio: UsuarioRepositorio): State<UsuarioUi?> {
    return produceState<UsuarioUi?>(initialValue = null, userId) {
        value = repositorio.buscar(userId)
    }
}

@Composable
fun PerfilScreen(userId: String, repositorio: UsuarioRepositorio) {
    val usuario by PerfilRemoto(userId, repositorio)

    if (usuario == null) {
        CircularProgressIndicator()
    } else {
        Text(usuario!!.nome)
    }
}

A chave userId cancela e reinicia o produtor quando muda. O bloco é cancelado automaticamente quando o Composable sai da composição.

Use produceState para adapters locais (Flow → State, listener → State). Para estado de tela com regras de negócio, testes e sobrevivência a configuração, continue no ViewModel com StateFlow. Não misture as duas fontes da verdade na mesma tela.

snapshotFlow: observando State como Flow

snapshotFlow converte leituras de estado do Compose em um Flow. Isso é útil para analytics, debounce ou operadores de Flow sem criar recomposição desnecessária:

@Composable
fun Busca(onBuscar: suspend (String) -> Unit) {
    var query by rememberSaveable { mutableStateOf("") }

    LaunchedEffect(Unit) {
        snapshotFlow { query }
            .debounce(300)
            .distinctUntilChanged()
            .collectLatest { texto ->
                if (texto.length >= 3) onBuscar(texto)
            }
    }

    TextField(value = query, onValueChange = { query = it })
}

O bloco de snapshotFlow deve apenas ler estado do Snapshot. Efeitos colaterais pertencem aos operadores seguintes ou ao coletor. Junto com produceState, forma o par State ↔ Flow: um transforma trabalho assíncrono em State; o outro observa State como Flow.

Erros comuns

  1. Criar mutableStateOf sem remember dentro do Composable: uma recomposição recria o holder e perde o valor.
  2. Duplicar a fonte da verdade: manter uma cópia no filho e outra no ViewModel produz sincronização difícil. Eleve o estado.
  3. Expor MutableStateFlow publicamente: qualquer consumidor passa a poder alterar a tela. Exponha StateFlow com asStateFlow().
  4. Mutar MutableList sem nova atribuição: a mudança interna pode não ser observada. Prefira coleções imutáveis ou SnapshotStateList.
  5. Usar remember para dados de negócio: a UI não deve ser o repositório de pedidos, usuários ou resultados de rede.
  6. Achar que ViewModel sobrevive à morte do processo: ele sobrevive à recriação por configuração, não ao encerramento do app pelo sistema.
  7. Coletar Flow sem considerar o Lifecycle no Android: em telas Android, prefira collectAsStateWithLifecycle().
  8. Usar derivedStateOf como cache universal: aplique quando o resultado muda menos que as entradas observadas, não em qualquer cálculo.
  9. Usar mutableStateOf(0) para contadores: prefira mutableIntStateOf (e equivalentes) para evitar boxing desnecessário.
  10. Colocar I/O pesado em produceState sem cancelamento claro: o produtor cancela ao sair da composição, mas regras de negócio complexas ainda pertencem ao ViewModel.

Perguntas frequentes

Toda mudança em State recompõe a tela inteira?

Não. O Compose rastreia os locais que leram o estado e agenda a recomposição dos escopos dependentes. Depois, ainda pode ignorar chamadas cujas entradas continuam estáveis e iguais.

StateFlow substitui mutableStateOf?

Não. Eles atuam bem em camadas diferentes. StateFlow é apropriado para o ViewModel e outras camadas baseadas em coroutines; MutableState é simples para estado local da UI Compose.

Devo colocar todo estado no ViewModel?

Não. Estado de negócio e estado de tela compartilhado costumam ficar no ViewModel. Estado efêmero de um componente — como um menu aberto — pode ficar no Composable se nenhuma outra parte precisar controlá-lo.

Qual a diferença entre estado e evento?

Estado descreve como a tela está agora e pode ser relido, como carregando = true. Evento representa algo que aconteceu, como “mostrar snackbar”. Tratar evento como estado sem uma estratégia de consumo pode repeti-lo após recriações.

mutableIntStateOf substitui mutableStateOf?

Só para os quatro primitivos suportados. Para String, listas, data classes e sealed types, continue com mutableStateOf. A escolha é de performance e clareza, não de semântica diferente de observação.

Quando preferir produceState em vez de ViewModel?

Quando o State é um adaptador local e curto (por exemplo, espelhar um Flow de sensor ou um callback de player). Se a tela tem regras de negócio, erros tipados, retry ou precisa de testes isolados, use ViewModel + StateFlow.

Próximos passos