Estabilidade no Compose

O Compose considera que os tipos são estáveis ou instáveis. Um tipo é estável quando é imutável ou se é possível que o Compose saiba se o valor dele mudou entre as recomposições. Um tipo é instável quando o Compose não consegue saber se o valor mudou entre as recomposições.

O Compose usa a estabilidade dos parâmetros de um elemento combinável para determinar se ele pode ignorar o elemento durante a recomposição:

  • Parâmetros estáveis:se um elemento combinável tiver parâmetros estáveis que não foram modificados, o Compose os ignorará.
  • Parâmetros instáveis:quando um elemento combinável tem parâmetros instáveis, o Compose sempre faz a recomposição do pai do componente.

Caso seu app inclua muitos componentes desnecessariamente instáveis que o Compose sempre recompõe, é possível que você observe problemas de desempenho e outros problemas.

Este documento explica como aumentar a estabilidade do app para melhorar o desempenho e a experiência geral do usuário.

Objetos imutáveis

Os snippets abaixo demonstram os princípios gerais por trás de estabilidade e recomposição.

A classe Contact é imutável. Isso ocorre porque todos os parâmetros são primitivos definidos com a palavra-chave val. Depois de criar uma instância do Contact, não é possível alterar o valor das propriedades do objeto. Se você tentar fazer isso, crie um novo objeto.

data class Contact(val name: String, val number: String)

O elemento combinável ContactRow tem um parâmetro do tipo Contact.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

Considere o que acontece quando o usuário clica no botão de ativação e o estado selected muda:

  1. O Compose avalia se precisa recompor o código dentro de ContactRow.
  2. Ela vê que o único argumento para ContactDetails é do tipo Contact.
  3. Como Contact é uma classe de dados imutável, o Compose tem certeza de que nenhum dos argumentos de ContactDetails mudou.
  4. Como tal, o Compose ignora a ContactDetails e não a recompõe.
  5. Por outro lado, os argumentos de ToggleButton mudaram, e o Compose faz a recomposição desse componente.

Objetos mutáveis

O exemplo anterior usa um objeto imutável, mas é possível criar um objeto mutável. Considere este snippet:

data class Contact(var name: String, var number: String)

Como cada parâmetro de Contact agora é um var, a classe não é mais imutável. Se as propriedades mudassem, o Compose não ficaria ciente. Isso ocorre porque o Compose só rastreia mudanças nos objetos de estado do Compose.

O Compose considera essa classe instável. O Compose não pula a recomposição de classes instáveis. Dessa forma, se Contact fosse definido dessa maneira, ContactRow no exemplo anterior seria recomposto sempre que selected mudasse.

Implementação no Compose

Pode ser útil, embora não crucial, considerar como exatamente o Compose determina quais funções serão ignoradas durante a recomposição.

Quando o compilador do Compose é executado no código, ele marca cada função e tipo com uma das várias tags. Essas tags refletem como o Compose processa a função ou o tipo durante a recomposição.

Funções

O Compose pode marcar funções como skippable ou restartable. Observe que ele pode marcar uma função como uma, ambas ou nenhuma delas:

  • Pulável: se o compilador marcar um elemento combinável como pulável, o Compose poderá pular durante a recomposição se todos os argumentos forem iguais aos valores anteriores.
  • Reiniciável: um elemento combinável que pode ser reiniciado serve como um "escopo" em que a recomposição pode ser iniciada. Em outras palavras, a função pode ser um ponto de entrada em que o Compose pode começar a executar novamente o código para recomposição após as mudanças de estado.

Tipos

O Compose marca os tipos como imutáveis ou estáveis. Cada tipo é um ou outro:

  • Imutável: o Compose marca um tipo como imutável se o valor das propriedades nunca pode mudar e todos os métodos são referencialmente transparentes.
    • Todos os tipos primitivos são marcados como imutáveis. Eles incluem String, Int e Float.
  • Estável: indica um tipo cujas propriedades podem mudar após a construção. Se e quando essas propriedades mudarem durante a execução, o Compose vai identificar essas mudanças.

Depurar a estabilidade

Se o app estiver recompondo um elemento combinável cujos parâmetros não foram modificados, primeiro verifique a definição para conferir se há parâmetros claramente mutáveis. O Compose sempre recompõe um componente se você transmitir um tipo com propriedades var ou uma propriedade val que usa um tipo instável conhecido.

Para informações detalhadas sobre como diagnosticar problemas complexos com estabilidade no Compose, consulte o guia Depurar a estabilidade.

Corrigir problemas de estabilidade

Para informações sobre como trazer estabilidade à implementação do Compose, consulte o guia Corrigir problemas de estabilidade.

Resumo

No geral, você deve observar os seguintes pontos:

  • Parâmetros: o Compose determina a estabilidade de cada parâmetro dos elementos combináveis para determinar quais elementos ele precisa pular durante a recomposição.
  • Correções imediatas: se você perceber que a função combinável não está sendo ignorada e está causando um problema de desempenho, verifique primeiro as causas óbvias de instabilidade, como parâmetros var.
  • Relatórios do compilador: use os relatórios do compilador para determinar qual estabilidade está sendo inferida sobre suas classes.
  • Coleções: o Compose sempre considera as classes de coleção instáveis, como List, Set e Map. Isso ocorre porque não é possível garantir que eles sejam imutáveis. Você pode usar coleções imutáveis do Kotlinx ou anotar suas classes como @Immutable ou @Stable.
  • Outros módulos: o Compose sempre considera instáveis quando eles vêm de módulos em que o compilador do Compose não é executado. Una as classes em classes de modelo de interface, se necessário.

Leia mais