O que é Composable em Kotlin?

Uma função Composable é uma função anotada com @Composable que descreve parte da interface do usuário no Jetpack Compose, o toolkit moderno de UI declarativa do Android. Em vez de montar telas com XML e manipular Views programaticamente, você escreve funções Kotlin que descrevem como a UI deve parecer para um dado estado.

O Compose e inspirado em frameworks como React e Flutter, mas aproveita todo o poder do Kotlin: type safety, extension functions, coroutines é uma sintaxe concisa que faz a construcao de interfaces parecer natural.

Sintaxe básica

import androidx.compose.runtime.Composable
import androidx.compose.material3.Text

@Composable
fun Saudacao(nome: String) {
    Text(text = "Ola, $nome!")
}

A anotacao @Composable informa ao compilador do Compose que essa função descreve UI e pode ser recomposta (re-executada) quando os dados mudam. Funções Composable só podem ser chamadas dentro de outros contextos Composable.

Composição de funções

O poder do Compose esta na composição. Você cria componentes pequenos e os combina para formar telas completas:

@Composable
fun CartaoDeUsuario(nome: String, email: String) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(
            text = nome,
            style = MaterialTheme.typography.headlineSmall
        )
        Spacer(modifier = Modifier.height(4.dp))
        Text(
            text = email,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}

@Composable
fun ListaDeUsuarios(usuarios: List<Usuario>) {
    LazyColumn {
        items(usuarios) { usuario ->
            CartaoDeUsuario(
                nome = usuario.nome,
                email = usuario.email
            )
        }
    }
}

Cada função Composable e um bloco reutilizavel. CartaoDeUsuario não sabe onde sera usado, e ListaDeUsuarios não sabe como o cartao e renderizado internamente. Essa separação torna o código modular e testavel.

Estado em Composables

Funções Composable são reexecutadas quando o estado muda. Para gerenciar estado local, use remember e mutableStateOf:

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

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Contagem: $contagem")
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick = { contagem++ }) {
            Text("Incrementar")
        }
    }
}

Quando contagem muda, o Compose recompoe apenas as partes da UI que dependem desse valor. Isso e feito de forma inteligente pelo runtime do Compose, que rastreia quais Composables leem quais estados.

Recomposicao

Recomposicao e o processo pelo qual o Compose reexecuta funções Composable quando os dados de entrada mudam. E importante entender que:

  • A recomposicao pode acontecer a qualquer momento e em qualquer ordem.
  • Funções Composable devem ser idempotentes: chamar com os mesmos parametros deve produzir o mesmo resultado.
  • Funções Composable não devem ter efeitos colaterais não controlados.
// Correto: sem efeitos colaterais
@Composable
fun ExibirNome(nome: String) {
    Text(text = nome)
}

// Incorreto: efeito colateral na recomposicao
@Composable
fun ExibirNomeComLog(nome: String) {
    println("Recompondo...") // Pode ser chamado muitas vezes!
    Text(text = nome)
}

Para efeitos colaterais controlados, use APIs como LaunchedEffect, SideEffect e DisposableEffect.

Efeitos colaterais controlados

@Composable
fun TelaDeDetalhes(userId: String) {
    var usuario by remember { mutableStateOf<Usuario?>(null) }

    LaunchedEffect(userId) {
        // Executa quando userId muda
        usuario = repositorio.buscarUsuario(userId)
    }

    usuario?.let { u ->
        Column {
            Text(text = u.nome)
            Text(text = u.email)
        }
    }
}

LaunchedEffect lanca uma coroutine que e cancelada e reiniciada quando a chave (userId) muda. Isso garante que efeitos colaterais sejam gerenciados corretamente no ciclo de vida do Composable.

Modifiers

Modifiers são o mecanismo do Compose para decorar e configurar componentes:

@Composable
fun CartaoEstilizado() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .clickable { /* acao */ }
    ) {
        Text(
            text = "Conteudo do cartao",
            modifier = Modifier.padding(24.dp)
        )
    }
}

A ordem dos modifiers importa: padding antes de clickable produz resultado diferente de clickable antes de padding.

Quando usar Composables

Composables são usados em qualquer projeto Android que adote Jetpack Compose:

  • Telas de aplicativos: construir layouts completos de forma declarativa.
  • Componentes reutilizaveis: criar uma biblioteca de componentes de UI consistentes.
  • Previews: usar @Preview para visualizar componentes no Android Studio sem rodar o app.
  • Testes de UI: Composables são facilmente testáveis com a biblioteca de testes do Compose.
  • Compose Multiplatform: o mesmo conceito funciona em desktop, web e iOS com Kotlin Multiplatform.

Casos de Uso no Mundo Real

  1. Design systems corporativos: empresas criam bibliotecas de Composables reutilizaveis (botoes, campos de texto, cartoes, modais) que encapsulam regras de design e acessibilidade. Equipes de produto consomem esses componentes para garantir consistencia visual em dezenas de telas sem duplicar código de estilizacao.

  2. Telas de formularios dinâmicos: aplicações que renderizam formularios baseados em configuracoes do servidor (como cadastros, pesquisas ou checklists) usam Composables parametrizados que recebem a definicao dos campos e geram a interface automaticamente. Cada tipo de campo (texto, selecao, data) e um Composable independente composto pela tela pai.

  3. Dashboards com gráficos e metricas: aplicações de analytics e monitoramento constroem paineis interativos usando Composables que reagem a mudancas de estado em tempo real. Graficos, indicadores e tabelas sao componentes Compose que se recompoem automaticamente quando os dados sao atualizados via Flow ou StateFlow do ViewModel.

  4. aplicações multiplataforma com Compose Multiplatform: projetos que compartilham a mesma base de código Kotlin entre Android, desktop (Windows, macOS, Linux) e iOS usam Composables como a camada de UI unificada. Um mesmo componente de lista ou navegação funciona em todas as plataformas com adaptacoes minimas.

Boas Praticas

  • Aplique state hoisting por padrão: Composables devem receber o estado como parametro e emitir eventos via callbacks. Isso torna o componente stateless, reutilizavel e facilmente testavel com a biblioteca de testes do Compose.
  • Mantenha Composables pequenos e focados em uma única responsabilidade. Um Composable que faz muita coisa (busca dados, processa lógica e renderiza UI) e difícil de testar e reutilizar. Delegue lógica de negócio ao ViewModel.
  • Use @Preview com múltiplas configuracoes (temas claro e escuro, diferentes tamanhos de tela, dados de exemplo variados) para validar a aparencia do componente sem precisar compilar e executar o aplicativo inteiro.
  • Evite efeitos colaterais diretos dentro de Composables. Use LaunchedEffect para coroutines, SideEffect para código sincrono que precisa executar apos a composicao e DisposableEffect para recursos que precisam de limpeza.
  • Preste atencao na ordem dos Modifiers: cada modifier e aplicado em sequência e afeta o resultado visual de forma cumulativa. Por exemplo, padding antes de background adiciona espaco dentro da area colorida, enquanto a ordem inversa adiciona espaco fora.

Perguntas Frequentes

P: Qual a diferenca entre remember e rememberSaveable no Compose? R: remember preserva o estado entre recomposicoes, mas o valor se perde em mudancas de configuração (como rotação de tela) ou quando o processo e destruído. rememberSaveable salva o estado automaticamente no SavedStateHandle, sobrevivendo a mudancas de configuração e até a morte do processo, desde que o valor seja serializavel.

P: Composables podem ser usados fora do Android? R: Sim. Com o Compose Multiplatform da JetBrains, você pode usar Composables em aplicações desktop (JVM), web (via Kotlin/Wasm) e iOS. A API de composicao e recomposicao e a mesma; o que muda sao os componentes de plataforma subjacentes que renderizam os elementos visuais.

P: Como testar Composables de forma automatizada? R: Use a biblioteca compose-ui-test que fornece a ComposeTestRule. Com ela você pode renderizar Composables isolados, interagir com elementos via onNodeWithText e performClick, e verificar estados com assertivas como assertIsDisplayed. Testes de Composable executam no JVM sem precisar de um emulador.

P: Por que meu Composable esta recompondo mais vezes do que o esperado? R: Recomposicoes excessivas geralmente acontecem quando o estado e lido em um escopo mais amplo do que necessário, quando lambdas sao recriadas a cada recomposicao (use remember para estabiliza-las), ou quando objetos de dados não implementam equals corretamente. Use o Layout Inspector do Android Studio para identificar quais Composables estao recompondo e com que frequência.

Erros comuns

  1. Colocar lógica de negócio dentro do Composable: Composables devem apenas descrever UI. Logica de negócio pertence ao ViewModel ou a camadas inferiores.

  2. Esquecer remember: sem remember, o estado e recriado a cada recomposicao, perdendo o valor anterior.

// Errado: contagem volta a 0 a cada recomposicao
@Composable
fun ContadorQuebrado() {
    var contagem = mutableStateOf(0) // Falta remember!
    // ...
}
  1. Efeitos colaterais descontrolados: fazer chamadas de rede, acessar banco de dados ou modificar estado global diretamente dentro de um Composable sem usar LaunchedEffect ou similar.

  2. Nao elevar o estado (state hoisting): manter estado em componentes filhos quando ele deveria ser controlado pelo pai. Isso dificulta reutilização e testes.

  3. Ignorar a ordem dos Modifiers: como modifiers são aplicados em cadeia, a ordem afeta o resultado visual. Testar e visualizar com @Preview ajuda a pegar esses problemas cedo.

State hoisting

Um padrão fundamental em Compose e elevar o estado para o componente pai:

// Componente stateless (sem estado proprio)
@Composable
fun CampoDeTexto(
    valor: String,
    onValorMudou: (String) -> Unit
) {
    TextField(
        value = valor,
        onValueChange = onValorMudou
    )
}

// Componente pai controla o estado
@Composable
fun Formulario() {
    var nome by remember { mutableStateOf("") }
    CampoDeTexto(
        valor = nome,
        onValorMudou = { nome = it }
    )
}

Isso torna CampoDeTexto reutilizavel e testavel, pois ele não gerencia seu próprio estado.

Termos relacionados

  • State: o mecanismo de estado reativo que aciona recomposicoes quando valores mudam.
  • Modifier: objeto que configura aparencia e comportamento de componentes Compose.
  • ViewModel: gerencia estado e lógica de negócio fora da camada de UI.
  • LaunchedEffect: API para executar efeitos colaterais controlados dentro de Composables.
  • Recomposicao: processo de reexecucao de funções Composable quando seus inputs mudam.
  • remember: função que preserva estado entre recomposicoes.

Composables representam uma mudanca de paradigma na construcao de interfaces em Kotlin. Ao tratar a UI como função do estado, o Compose elimina toda uma classe de bugs relacionados a sincronizacao entre dados e interface, tornando o desenvolvimento mais previsivel e produtivo.