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.
Navegacao no Compose
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
keyem LazyColumn: sem keys, o Compose pode recompor itens incorretamente ao inserir ou remover elementos. - Evite efeitos colaterais em composables: use
LaunchedEffect,SideEffectouDisposableEffectpara operacoes que nao sao puras. - Prefira
Modifiercomo primeiro parametro opcional: e uma convencao do Compose que permite composicao flexivel de estilos. - Extraia preview functions: crie funcoes
@Previewseparadas 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
rememberpara estabilizar callbacks. - Esquecer o
remember: semremember, o estado e resetado a cada recomposicao, causando bugs dificeis de rastrear. - Modificar estado durante a composicao: alterar
mutableStateOfdentro 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.