O Jetpack Compose revolucionou a forma como construimos interfaces no Android. Abandonando o sistema de XML que dominou a plataforma por mais de uma decada, o Compose adota um paradigma declarativo onde a UI e descrita como funcoes Kotlin puras. Em vez de manipular views imperativamente, voce declara como a tela deve parecer para cada estado, e o framework cuida das atualizacoes automaticamente. Este guia apresenta tudo o que voce precisa saber para dominar o Jetpack Compose, desde conceitos fundamentais ate padroes avancados usados em producao.

Primeiros Passos com Compose

Para utilizar o Compose, adicione as dependencias necessarias ao build.gradle.kts:

android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.8"
    }
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:2024.02.00")
    implementation(composeBom)
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.activity:activity-compose:1.8.2")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

Composables Fundamentais

No Compose, cada elemento de UI e uma funcao anotada com @Composable. Os componentes basicos incluem Text, Button, Image, TextField e layouts como Column, Row e Box:

@Composable
fun CartaoProduto(
    nome: String,
    preco: Double,
    onComprarClick: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(
                text = nome,
                style = MaterialTheme.typography.headlineSmall,
                fontWeight = FontWeight.Bold
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = "R$ %.2f".format(preco),
                style = MaterialTheme.typography.bodyLarge,
                color = MaterialTheme.colorScheme.primary
            )
            Spacer(modifier = Modifier.height(16.dp))
            Button(
                onClick = onComprarClick,
                modifier = Modifier.fillMaxWidth()
            ) {
                Text("Adicionar ao Carrinho")
            }
        }
    }
}

Cada composable recebe parametros e renderiza a UI correspondente. Nao ha heranca ou XML envolvido – tudo e Kotlin.

Gerenciamento de Estado

O estado e o conceito mais importante do Compose. Quando o estado muda, o Compose recompoe (re-renderiza) apenas os composables afetados:

@Composable
fun ContadorSimples() {
    var contador by remember { mutableStateOf(0) }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.padding(32.dp)
    ) {
        Text(
            text = "Contagem: $contador",
            style = MaterialTheme.typography.displayMedium
        )
        Spacer(modifier = Modifier.height(16.dp))
        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = { contador-- }) {
                Text("Diminuir")
            }
            Button(onClick = { contador++ }) {
                Text("Aumentar")
            }
        }
    }
}

O remember preserva o estado entre recomposicoes, enquanto mutableStateOf cria um estado observavel. Para preservar o estado entre mudancas de configuracao, use rememberSaveable.

State Hoisting

O padrao state hoisting consiste em elevar o estado para o composable pai, tornando os filhos stateless e reutilizaveis:

@Composable
fun FormularioCadastro() {
    var nome by rememberSaveable { mutableStateOf("") }
    var email by rememberSaveable { mutableStateOf("") }
    var senhaVisivel by remember { mutableStateOf(false) }

    FormularioConteudo(
        nome = nome,
        onNomeChange = { nome = it },
        email = email,
        onEmailChange = { email = it },
        senhaVisivel = senhaVisivel,
        onToggleSenha = { senhaVisivel = !senhaVisivel }
    )
}

@Composable
fun FormularioConteudo(
    nome: String,
    onNomeChange: (String) -> Unit,
    email: String,
    onEmailChange: (String) -> Unit,
    senhaVisivel: Boolean,
    onToggleSenha: () -> Unit
) {
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = nome,
            onValueChange = onNomeChange,
            label = { Text("Nome completo") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(8.dp))
        OutlinedTextField(
            value = email,
            onValueChange = onEmailChange,
            label = { Text("E-mail") },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Email
            ),
            modifier = Modifier.fillMaxWidth()
        )
    }
}

O FormularioConteudo e totalmente reutilizavel e testavel, pois nao gerencia estado proprio.

Listas com LazyColumn

O equivalente ao RecyclerView no Compose e o LazyColumn, que renderiza apenas os itens visiveis:

@Composable
fun ListaProdutos(
    produtos: List<Produto>,
    onProdutoClick: (Produto) -> Unit
) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = produtos,
            key = { it.id }
        ) { produto ->
            CartaoProduto(
                nome = produto.nome,
                preco = produto.preco,
                onComprarClick = { onProdutoClick(produto) }
            )
        }
    }
}

O parametro key e fundamental para performance, pois ajuda o Compose a identificar quais itens mudaram, foram adicionados ou removidos.

O Navigation Compose permite navegacao entre telas de forma declarativa:

@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = "lista"
    ) {
        composable("lista") {
            TelaLista(
                onProdutoClick = { produtoId ->
                    navController.navigate("detalhe/$produtoId")
                }
            )
        }
        composable(
            route = "detalhe/{produtoId}",
            arguments = listOf(
                navArgument("produtoId") { type = NavType.LongType }
            )
        ) { backStackEntry ->
            val produtoId = backStackEntry.arguments?.getLong("produtoId") ?: 0L
            TelaDetalhe(
                produtoId = produtoId,
                onVoltarClick = { navController.popBackStack() }
            )
        }
    }
}

Integracao com ViewModel

Compose se integra naturalmente com ViewModels e StateFlow:

@Composable
fun TelaProdutos(
    viewModel: ProdutoViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        is ProdutoUiState.Loading -> {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                CircularProgressIndicator()
            }
        }
        is ProdutoUiState.Success -> {
            ListaProdutos(
                produtos = state.produtos,
                onProdutoClick = { viewModel.selecionarProduto(it) }
            )
        }
        is ProdutoUiState.Error -> {
            MensagemErro(
                mensagem = state.mensagem,
                onTentarNovamente = { viewModel.carregarProdutos() }
            )
        }
    }
}

O collectAsStateWithLifecycle e a forma recomendada de coletar Flows no Compose, pois respeita o ciclo de vida.

Temas e Material Design 3

O Compose integra nativamente com Material Design 3, permitindo personalizar cores, tipografia e formas:

@Composable
fun MeuAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) {
        darkColorScheme(
            primary = Color(0xFF7C4DFF),
            secondary = Color(0xFF03DAC5),
            background = Color(0xFF121212)
        )
    } else {
        lightColorScheme(
            primary = Color(0xFF6200EE),
            secondary = Color(0xFF03DAC5),
            background = Color(0xFFFFFBFE)
        )
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography(),
        content = content
    )
}

Boas Praticas com Jetpack Compose

  • Composables pequenos e focados: cada composable deve ter uma unica responsabilidade. Quebre telas complexas em componentes menores.
  • State hoisting sempre que possivel: composables stateless sao mais reutilizaveis e testaveis.
  • Use key em LazyColumn: sem keys, o Compose pode recompor itens incorretamente ao inserir ou remover elementos.
  • Evite efeitos colaterais em composables: use LaunchedEffect, SideEffect ou DisposableEffect para operacoes que nao sao puras.
  • Prefira Modifier como primeiro parametro opcional: e uma convencao do Compose que permite composicao flexivel de estilos.
  • Extraia preview functions: crie funcoes @Preview separadas para visualizar componentes no Android Studio sem rodar o app.

Erros Comuns e Armadilhas

  • Recomposicao excessiva: passar lambdas nao estabilizadas ou objetos que mudam referencia a cada recomposicao causa recomposicoes desnecessarias. Use remember para estabilizar callbacks.
  • Esquecer o remember: sem remember, o estado e resetado a cada recomposicao, causando bugs dificeis de rastrear.
  • Modificar estado durante a composicao: alterar mutableStateOf dentro do corpo de um composable sem estar em um callback causa loops infinitos de recomposicao.
  • Usar indices como keys: em LazyColumn, usar o indice da lista como key impede otimizacoes e causa comportamento incorreto quando itens sao reordenados.
  • Nao tratar loading e error states: toda tela que carrega dados deve ter estados visuais para carregamento e erro, melhorando a experiencia do usuario.

Conclusao e Proximos Passos

O Jetpack Compose representa o futuro do desenvolvimento de interfaces no Android. Sua abordagem declarativa, combinada com a expressividade do Kotlin, resulta em codigo mais conciso, legivel e facil de manter. Dominar conceitos como gerenciamento de estado, recomposicao e state hoisting e essencial para construir aplicacoes eficientes.

Como proximos passos, explore animacoes no Compose, aprofunde-se em testes de UI com ComposeTestRule e estude a integracao com Compose Multiplatform para compartilhar UI entre Android, iOS e desktop. Cada passo amplia as possibilidades do que voce pode construir com essa tecnologia.