O que é State em Kotlin?

No contexto do Jetpack Compose, State (estado) é um valor que, quando muda, aciona automaticamente a recomposicao das funções Composable que o leem. O Compose rastreia quais Composables dependem de quais estados e reexecuta apenas os afetados quando o estado muda.

Sem estado reativo, a UI seria estática. Com State, você declara como a UI deve parecer para cada possível valor do estado, é o Compose cuida de manter tudo sincronizado.

Criando estado com mutableStateOf

import androidx.compose.runtime.*

@Composable
fun Contador() {
    // Cria um estado reativo que sobrevive a recomposicoes
    var contagem by remember { mutableStateOf(0) }

    Column {
        Text(text = "Contagem: $contagem")
        Button(onClick = { contagem++ }) {
            Text("Incrementar")
        }
    }
}

Quando contagem muda (via contagem++), o Compose detecta a mudanca e reexecuta Contador(), atualizando o Text com o novo valor.

Tres elementos trabalham juntos:

  • mutableStateOf(0): cria um holder de estado reativo com valor inicial 0.
  • remember: preserva o estado entre recomposicoes (sem remember, o estado seria recriado a cada vez).
  • by: delegação de propriedade que permite ler e escrever o valor diretamente.

remember vs rememberSaveable

@Composable
fun CampoDeTexto() {
    // remember: sobrevive a recomposicoes, mas NAO a mudancas de configuracao
    var texto by remember { mutableStateOf("") }

    // rememberSaveable: sobrevive a recomposicoes E mudancas de configuracao
    var textoSalvo by rememberSaveable { mutableStateOf("") }

    Column {
        TextField(value = texto, onValueChange = { texto = it })
        TextField(value = textoSalvo, onValueChange = { textoSalvo = it })
    }
}

Use rememberSaveable quando o estado precisa sobreviver a rotação de tela ou a recriação da Activity.

State hoisting (elevacao de estado)

O padrão fundamental de gerenciamento de estado em Compose e elevar o estado para o componente pai:

// Componente stateless: recebe estado e eventos como parametros
@Composable
fun CampoDeNome(
    nome: String,
    onNomeMudou: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    OutlinedTextField(
        value = nome,
        onValueChange = onNomeMudou,
        label = { Text("Nome") },
        modifier = modifier.fillMaxWidth()
    )
}

// Componente pai: controla o estado
@Composable
fun FormularioDeCadastro() {
    var nome by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }

    Column(modifier = Modifier.padding(16.dp)) {
        CampoDeNome(
            nome = nome,
            onNomeMudou = { nome = it }
        )
    }
}

Beneficios do state hoisting:

  • O componente filho e reutilizavel e testavel.
  • O pai tem controle total sobre o estado.
  • O fluxo de dados e unidirecional: estado desce, eventos sobem.

derivedStateOf

Para estados que são derivados de outros estados:

@Composable
fun ListaComFiltro() {
    var busca by remember { mutableStateOf("") }
    val todosItens = remember { listOf("Kotlin", "Java", "Python", "Rust", "Go") }

    // Recalcula apenas quando 'busca' ou 'todosItens' mudam
    val itensFiltrados by remember(busca) {
        derivedStateOf {
            todosItens.filter { it.contains(busca, ignoreCase = true) }
        }
    }

    Column {
        TextField(value = busca, onValueChange = { busca = it })
        itensFiltrados.forEach { item ->
            Text(text = item)
        }
    }
}

derivedStateOf evita recomposicoes desnecessarias quando o calculo derivado produz o mesmo resultado.

Estado com ViewModel

Para estado que sobrevive a mudancas de configuração e representa lógica de negócio:

class ListaViewModel : ViewModel() {
    private val _itens = MutableStateFlow<List<String>>(emptyList())
    val itens: StateFlow<List<String>> = _itens.asStateFlow()

    private val _carregando = MutableStateFlow(false)
    val carregando: StateFlow<Boolean> = _carregando.asStateFlow()

    fun carregar() {
        viewModelScope.launch {
            _carregando.value = true
            _itens.value = repositorio.buscarItens()
            _carregando.value = false
        }
    }
}

@Composable
fun ListaTela(viewModel: ListaViewModel = viewModel()) {
    val itens by viewModel.itens.collectAsState()
    val carregando by viewModel.carregando.collectAsState()

    if (carregando) {
        CircularProgressIndicator()
    } else {
        LazyColumn {
            items(itens) { item -> Text(text = item) }
        }
    }
}

collectAsState() converte um StateFlow em State do Compose, integrando o ViewModel com a recomposicao.

Tipos de estado

// MutableState: estado simples do Compose
val estado: MutableState<Int> = mutableStateOf(0)

// StateFlow: estado observavel de coroutines
val fluxo: StateFlow<Int> = MutableStateFlow(0)

// LiveData: estado observavel do Android (legado)
val liveData: LiveData<Int> = MutableLiveData(0)

// Todos podem ser convertidos para State do Compose:
val a by remember { mutableStateOf(0) }
val b by fluxo.collectAsState()
val c by liveData.observeAsState(0)

snapshotFlow: State para Flow

Para observar mudancas de State como um Flow:

@Composable
fun BuscaComDebounce() {
    var query by remember { mutableStateOf("") }

    LaunchedEffect(Unit) {
        snapshotFlow { query }
            .debounce(300)
            .distinctUntilChanged()
            .collect { busca ->
                // Executar busca apos 300ms sem digitacao
                buscar(busca)
            }
    }

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

Quando usar cada tipo de estado

TipoUsoEscopo
remember + mutableStateOfEstado de UI localComposable
rememberSaveableEstado que sobrevive rotaçãoComposable
derivedStateOfEstado calculado de outros estadosComposable
ViewModel + StateFlowLogica de negócio e dadosTela
snapshotFlowConverter State em FlowBridge

Casos de Uso no Mundo Real

  1. Formularios reativos: em telas de cadastro, login ou checkout, cada campo (nome, email, senha, endereco) e representado por um mutableStateOf. Mudancas no texto atualizam o estado, que por sua vez aciona recomposicao dos componentes visuais, incluindo validacao em tempo real e habilitacao/desabilitacao do botao de envio.

  2. Telas com carregamento e paginação: aplicações que consomem APIs usam StateFlow no ViewModel para representar estados como carregando, sucesso, erro e lista vazia. O Composable coleta esse fluxo com collectAsState() e renderiza a UI apropriada para cada estado, incluindo indicadores de progresso e mensagens de erro.

  3. Temas e preferencias do usuário: aplicações que permitem alternar entre tema claro e escuro ou alterar tamanho de fonte usam estado global (geralmente via CompositionLocal ou ViewModel compartilhado) para propagar mudancas de configuração por toda a arvore de Composables, recompondo apenas os componentes afetados.

  4. Jogos e animacoes interativas: aplicações que exibem animacoes baseadas em interação do usuário (arrastar, pincar, rotacionar) usam mutableStateOf combinado com Animatable para rastrear posicao, escala e rotação de elementos visuais, com recomposicao automatica a cada frame de animacao.

Boas Praticas

  • Eleve o estado (state hoisting) sempre que um componente pai precisar controlar ou observar o valor: mantenha componentes filhos stateless, recebendo estado e callbacks como parametros. Isso torna os componentes reutilizaveis, testáveis e com fluxo de dados previsivel.
  • Use derivedStateOf para calculos derivados de outros estados: em vez de recompor toda vez que um estado base muda, derivedStateOf recalcula o valor derivado apenas quando necessário e só aciona recomposicao se o resultado realmente mudar.
  • Separe estado de UI de estado de negócio: estado de UI local (como se um dropdown esta aberto) deve ficar no Composable com remember. Estado de negócio (como a lista de itens carregados do servidor) deve ficar no ViewModel com StateFlow.
  • Nunca mute colecoes dentro de mutableStateOf diretamente: o Compose detecta mudancas por referência. Alterar o conteudo de uma MutableList sem criar uma nova instancia não aciona recomposicao. Sempre crie uma nova colecao ao modificar.
  • Prefira rememberSaveable para estado que precisa sobreviver a rotação de tela: remember perde o valor quando a Activity e recriada. Para dados como texto digitado pelo usuário, use rememberSaveable para preservar o estado automaticamente.

Perguntas Frequentes

P: Qual a diferenca entre mutableStateOf e MutableStateFlow? R: mutableStateOf e a primitiva de estado do Compose, integrada diretamente ao sistema de recomposicao. MutableStateFlow e uma primitiva de coroutines que representa um fluxo de estado. No ViewModel, prefira StateFlow para expor estado (pois o ViewModel não deve depender do Compose). No Composable, converta para State com collectAsState().

P: Quando devo usar remember vs rememberSaveable vs ViewModel? R: Use remember para estado de UI efemero que não precisa sobreviver a rotação (ex: se um tooltip esta visivel). Use rememberSaveable para estado de UI que deve sobreviver a mudancas de configuração (ex: texto digitado em um campo). Use ViewModel para estado de negócio e dados carregados de APIs ou banco de dados.

P: Por que meu Composable não recompoe quando mudo o estado? R: As causas mais comuns sao: (1) esqueceu de usar remember, entao o estado e recriado a cada recomposicao; (2) esta mutando uma colecao existente em vez de criar uma nova; (3) o valor sendo comparado e estruturalmente igual ao anterior, entao o Compose não detecta mudanca; (4) o estado esta sendo lido fora do escopo de composicao (ex: em um LaunchedEffect sem dependência correta).

P: E seguro acessar mutableStateOf de várias threads? R: As leituras e escritas individuais em mutableStateOf sao thread-safe. Porem, operações compostas (ler e escrever baseado no valor lido) não sao atomicas. Para esses casos, use Snapshot.withMutableSnapshot ou gerencie a concorrencia no ViewModel com coroutines.

Erros comuns

  1. Esquecer remember: sem remember, o estado e recriado a cada recomposicao, perdendo o valor.
// ERRADO
@Composable
fun Quebrado() {
    var x = mutableStateOf(0) // Recriado toda recomposicao!
}

// CORRETO
@Composable
fun Correto() {
    var x by remember { mutableStateOf(0) }
}
  1. Colocar lógica de negócio no Composable: buscar dados, processar regras de negócio e acessar banco de dados devem ficar no ViewModel, não no Composable.

  2. Nao elevar estado quando necessário: manter estado em componentes filhos quando o pai precisa controla-lo dificulta a comunicação e reutilização.

  3. mutação direta de coleções: mudar o conteudo de uma MutableList dentro de um mutableStateOf não aciona recomposicao. Você precisa criar uma nova lista.

// ERRADO: nao aciona recomposicao
val lista = remember { mutableStateOf(mutableListOf(1, 2, 3)) }
lista.value.add(4) // Compose nao detecta!

// CORRETO: cria nova lista
val lista = remember { mutableStateOf(listOf(1, 2, 3)) }
lista.value = lista.value + 4 // Nova referência, Compose detecta
  1. Usar remember para estado que precisa sobreviver a rotação: remember não sobrevive a mudancas de configuração. Use rememberSaveable ou ViewModel.

Termos relacionados

  • Composable: função que lê State e e recomposta quando o estado muda.
  • Recomposicao: reexecucao de funções Composable quando seus estados de entrada mudam.
  • ViewModel: gerencia estado e lógica de negócio fora da camada de UI.
  • StateFlow: fluxo de estado de coroutines que pode ser convertido em State do Compose.
  • remember: função que preserva valores entre recomposicoes.
  • Flow: stream reativo que pode ser coletado como State usando collectAsState.

State e o conceito central do Jetpack Compose. Entender como criar, elevar, derivar e gerenciar estado e o que separa interfaces reativas e corretas de interfaces com bugs de sincronizacao. O modelo declarativo do Compose torna o gerenciamento de estado mais previsivel e menos propenso a erros do que a abordagem imperativa tradicional.