Dominando a recomposição
Parâmetros estáveis e menos recomposições.
Dominando a recomposição é uma aula grátis de Android Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Android Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Android Academy inclui 4 aulas no total.
Recomposição: o controle de desempenho do Compose
No Jetpack Compose, a interface é descrita por funções. Quando o estado muda, o Compose faz a recomposição: ele executa novamente os componentes que leem esse estado para atualizar a tela.
A recomposição é normal e barata quando seu escopo é bem definido. Porém, quando acontece com frequência excessiva ou abrange uma parte muito grande da árvore, torna-se a principal causa de engasgos no Compose. Nesta lição, você aprenderá a manter a recomposição pequena e rara.
Visualização das contagens de recomposição
Antes de corrigir, meça. O Layout Inspector no Android Studio mostra em tempo real as contagens de recomposição de cada componente. Você também pode usar as métricas do compilador do Compose ou um contador rápido de depuração.
Um truque simples: um SideEffect incrementa uma referência cada vez que um componente é recomposto, permitindo registrar surpresas durante o desenvolvimento.
@Composable
fun RecompositionCounter(tag: String) {
val count = remember { mutableStateOf(0) }
SideEffect { count.value++ }
Log.d("Recompose", "$tag recomposed ${count.value} times")
}
// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.Leia o estado o mais tarde possível
O Compose recompõe apenas os componentes que leem um valor de estado. Se um componente pai lê o estado, todo o pai é recomposto; se apenas um componente filho pequeno o lê, somente esse filho é recomposto.
Portanto, leve as leituras de estado para baixo na árvore. Aqui, a versão inadequada recompõe toda a Column a cada atualização; a versão adequada restringe isso ao rótulo.
// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
Column {
ExpensiveHeader()
Text("Elapsed: $seconds")
}
}
// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
Column {
ExpensiveHeader()
Text("Elapsed: ${seconds()}")
}
}Adie leituras com lambdas
Um padrão poderoso é, em vez de passar um valor que muda com frequência, passar uma lambda que o retorna. O componente que finalmente chama a lambda é o único que é recomposto.
É por isso que Modifier.offset { ... } e graphicsLayer { ... } recebem lambdas: os valores de rolagem e animação mudam a cada quadro, e a lambda mantém a recomposição totalmente fora da fase de layout.
// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))
// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })
// Same idea for alpha/scale during animation:
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)Estabilidade: por que o Compose ignora recomposições
O Compose pode ignorar a recomposição de um componente se todos os seus parâmetros forem estáveis e não tiverem mudado. Um tipo é estável quando o Compose pode confiar que equals reflete uma mudança real e que seus campos públicos não sofrem mutações não observadas.
- Estáveis: tipos primitivos,
String, classes de dados imutáveis eState. - Instáveis: interfaces
List/Map, classes com camposvare tipos de módulos sem o compilador.
Um parâmetro instável força a recomposição mesmo quando nada mudou.
// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }
data class User(val id: Long, val name: String) // stable: all valsTorne os parâmetros estáveis
Duas correções comuns para parâmetros instáveis:
- Use coleções imutáveis de
kotlinx.collections.immutable(por exemplo,ImmutableList), que o Compose trata como estáveis. - Anote uma classe sob seu controle com
@Immutableou@Stablepara garantir ao Compose que ela não mudará.
Agora o Compose pode ignorar com segurança a recomposição quando a mesma instância for passada novamente.
import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable
@Immutable
data class UiState(
val title: String,
val users: ImmutableList<User>
)
// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }remember: não recalcule a cada quadro
Os componentes podem ser executados muitas vezes. Qualquer cálculo não trivial feito diretamente no corpo é executado em cada recomposição. Envolva-o em remember para que seja recalculado somente quando suas chaves mudarem.
Para valores derivados de outro estado, prefira derivedStateOf, que só emite novamente quando o resultado calculado realmente muda.
// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }
// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()Chaves estáveis em listas Lazy
Em LazyColumn/LazyRow, atribua uma key estável a cada item. Sem chaves, inserir ou reordenar itens força o Compose a recompor e medir novamente itens que realmente não mudaram, porque ele os acompanha pela posição.
Com uma chave estável, o Compose consegue associar os itens entre as atualizações e ignorar os que não mudaram.
LazyColumn {
items(
items = users,
key = { user -> user.id } // stable identity
) { user ->
UserRow(user)
}
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.Eleve o estado e passe os eventos para cima
Parâmetros lambda instáveis também podem impedir que o Compose ignore recomposições se uma nova instância de lambda for criada a cada recomposição. Estabilize os retornos de chamada memorizando-os ou referenciando funções estáveis.
Combinado com a elevação de estado (o estado fica no chamador e os eventos sobem), isso mantém os componentes folha baratos e passíveis de serem ignorados.
@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
TextField(value = query, onValueChange = onQueryChange)
}
// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)Evite ler o estado de rolagem/animação em um nível muito alto
Um erro clássico é ler um estado que muda rapidamente (deslocamento da rolagem, progresso da animação) em um componente de nível alto. Isso força toda a subárvore a ser recomposta a cada quadro.
Mantenha essas leituras dentro das lambdas de Modifier (offset {}, graphicsLayer {}, drawBehind {}) para que o trabalho aconteça na fase de layout ou desenho, não na recomposição. Essa é a diferença entre uma rolagem fluida a 60 fps e uma rolagem com engasgos.
// Animated color used only for drawing -> stay in the draw phase
Box(
Modifier.drawBehind {
drawRect(color = animatedColor()) // lambda read, no recomposition
}
)Lista de verificação da recomposição
Quando uma tela parece apresentar engasgos durante a interação, percorra esta lista:
- Os parâmetros são estáveis (dados imutáveis,
ImmutableList)? - Você está lendo o estado que muda rapidamente o mais abaixo possível, idealmente em lambdas de modificadores?
- As listas Lazy têm chaves estáveis?
- Os cálculos dispendiosos estão protegidos por
remember/derivedStateOf? - O Layout Inspector confirmou que a contagem realmente diminuiu?
Cada item marcado remove trabalho desperdiçado de todos os quadros.
Verificação rápida
Você passa um List<User> para um componente e percebe que ele é recomposto mesmo quando os dados não mudaram. Qual é a correção mais direta?
Recapitulação: recomposição menor e mais rara
Você aprendeu a controlar a recomposição, o principal problema de desempenho do Compose:
- Meça as contagens com o Layout Inspector ou um contador de depuração.
- Leia o estado o mais abaixo possível; adie as leituras com lambdas.
- Deixe os parâmetros estáveis usando dados imutáveis e
@Immutable/ImmutableList. - Use
rememberederivedStateOfpara evitar recalcular a cada quadro. - Atribua chaves estáveis aos itens Lazy; mantenha as leituras de estado rápido nas lambdas de modificadores.
A seguir, passaremos da CPU/recomposição para a memória: como encontrar e corrigir vazamentos.
Perguntas Frequentes
A aula “Dominando a recomposição” é grátis?
Sim — o texto completo de “Dominando a recomposição” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Android Academy, atualize para CoddyKit PRO. O curso de Android Academy inclui 4 aulas no total.
O que vou aprender em “Dominando a recomposição”?
Parâmetros estáveis e menos recomposições. Você pratica Android Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Android Academy?
Nenhuma experiência prévia é necessária. Android Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “Dominando a recomposição”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Android Academy?
Sim. Cada aula de Android Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Medindo o desempenho
- Dominando a recomposição
- Vazamentos de memória e correções
- Inicialização e perfis de referência