Quando você se deparar com uma classe instável que causa problemas de desempenho, é preciso estabilizá-la. Este documento descreve várias técnicas que você pode usar para fazer isso.
Ativar a rejeição avançada
Primeiro, tente ativar o modo de rejeição avançada. O modo de rejeição avançada permite rejeitar elementos combináveis com parâmetros instáveis e é o método mais fácil para corrigir problemas de desempenho causados pela estabilidade.
Consulte Ignorar com força para mais informações.
Tornar a classe imutável
Você também pode tentar tornar uma classe instável completamente imutável.
- Imutável: indica um tipo em que o valor de qualquer propriedade nunca
muda depois que uma instância desse tipo é construída, e todos os métodos são
referencialmente transparentes.
- Verifique se todas as propriedades da classe são
valem vez devare de tipos imutáveis. - Tipos primitivos, como
String, InteFloat, são sempre imutáveis. - Se isso não for possível, use o estado do Compose para qualquer propriedade mutável.
- Verifique se todas as propriedades da classe são
- Estável: indica um tipo mutável. O tempo de execução do Compose não fica sabendo se e quando alguma das propriedades públicas ou do comportamento do método do tipo geraria resultados diferentes de uma invocação anterior.
Coleções imutáveis
Um motivo comum para o Compose considerar uma classe instável são as coleções. Conforme observado na página Diagnosticar problemas de estabilidade, o compilador do Compose não pode ter certeza absoluta de que coleções como List, Map e Set são realmente imutáveis e, portanto, as marca como instáveis.
Para resolver isso, use coleções imutáveis. O compilador do Compose inclui suporte para Kotlinx Immutable Collections. Essas coleções são garantidas como imutáveis, e o compilador do Compose as trata como tal. Essa biblioteca ainda está em versão Alfa. Portanto, é possível que a API dela mude.
Considere novamente esta classe instável do guia Diagnosticar problemas de estabilidade:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
É possível tornar tags estável usando uma coleção imutável. Na classe, mude o tipo de tags para ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Depois disso, todos os parâmetros da classe são imutáveis, e o compilador do Compose marca a classe como estável.
Adicione as anotações Stable ou Immutable.
Uma possível maneira de resolver problemas de estabilidade é anotar classes instáveis
com @Stable ou @Immutable.
A anotação de uma classe substitui o que o compilador inferiria sobre ela. É semelhante ao operador !! em Kotlin. Tenha muito cuidado ao usar essas anotações. Substituir o comportamento do compilador
pode levar a bugs imprevistos, como a não recomposição do elemento combinável quando
você espera que isso aconteça.
Se for possível tornar sua classe estável sem uma anotação, tente alcançar a estabilidade dessa forma.
O snippet a seguir mostra um exemplo mínimo de uma classe de dados com a anotação imutável:
@Immutable
data class Snack(
…
)
Se você usar a anotação @Immutable ou @Stable, o compilador do Compose
marcará a classe Snack como estável.
Classes anotadas em coleções
Considere um elemento combinável que inclui um parâmetro do tipo List<Snack>:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Mesmo que você anote Snack com @Immutable, o compilador do Compose ainda vai marcar o parâmetro snacks em HighlightedSnacks como instável.
Os parâmetros enfrentam o mesmo problema que as classes quando se trata de tipos de coleção. O compilador do Compose sempre marca um parâmetro do tipo List como instável, mesmo quando é uma coleção de tipos estáveis.
Não é possível marcar um parâmetro individual como estável nem anotar um combinável para que ele sempre possa ser ignorado. Há vários caminhos para seguir.
Há várias maneiras de contornar o problema das coleções instáveis. As subseções abaixo apresentam essas diferentes abordagens.
Arquivo de configuração
Se você quiser obedecer ao contrato de estabilidade na sua base de código, poderá
ativar a consideração das coleções Kotlin como estáveis adicionando
kotlin.collections.* ao arquivo de configuração de estabilidade.
Coleção imutável
Para ter segurança de imutabilidade no tempo de compilação, use uma
coleção imutável do kotlinx em vez de List.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Se não for possível usar uma coleção imutável, crie a sua. Para fazer isso, encapsule o List em uma classe estável anotada. Um wrapper genérico provavelmente é a melhor escolha para isso, dependendo dos seus requisitos.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Em seguida, use isso como o tipo do parâmetro no seu elemento combinável.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Solução
Depois de adotar uma dessas abordagens, o compilador do Compose vai marcar o
elemento combinável HighlightedSnacks como skippable e restartable.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
Durante a recomposição, o Compose agora pode ignorar HighlightedSnacks se nenhuma das
entradas tiver mudado.
Arquivo de configuração de estabilidade
A partir do Compose Compiler 1.5.5, é possível fornecer um arquivo de configuração de classes a serem consideradas estáveis no tempo de compilação. Isso permite considerar como estáveis classes que você não controla, como as da biblioteca padrão, como LocalDateTime.
O arquivo de configuração é um arquivo de texto simples com uma classe por linha. Comentários, curingas simples e duplos são aceitos.
Exemplo de configuração:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
Para ativar esse recurso, transmita o caminho do arquivo de configuração para o
bloco de opções composeCompiler da configuração do plug-in do Gradle do compilador do Compose.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Como o compilador do Compose é executado em cada módulo do projeto separadamente, você pode fornecer configurações diferentes para módulos diferentes, se necessário. Outra opção é ter uma configuração no nível raiz do projeto e transmitir esse caminho para cada módulo.
Vários módulos
Outro problema comum envolve a arquitetura de vários módulos. O compilador do Compose só pode inferir se uma classe é estável se todos os tipos não primitivos que ela referencia forem explicitamente marcados como estáveis ou estiverem em um módulo que também foi criado com o compilador do Compose.
Se a camada de dados estiver em um módulo separado da camada de UI, que é a abordagem recomendada, esse pode ser um problema que você vai encontrar.
Solução
Para resolver esse problema, siga uma destas abordagens:
- Adicione as classes ao arquivo de configuração do compilador.
- Ative o compilador do Compose nos módulos da camada de dados ou marque suas classes
com
@Stableou@Immutablequando apropriado.- Isso envolve adicionar uma dependência do Compose à sua camada de dados. No entanto, ela é apenas a dependência do tempo de execução do Compose e não do
Compose-UI.
- Isso envolve adicionar uma dependência do Compose à sua camada de dados. No entanto, ela é apenas a dependência do tempo de execução do Compose e não do
- No módulo de UI, encapsule as classes da camada de dados em classes wrapper específicas da UI.
O mesmo problema também ocorre ao usar bibliotecas externas se elas não usarem o compilador do Compose.
Nem todo elemento combinável precisa ser ignorável
Ao trabalhar para corrigir problemas de estabilidade, não tente tornar todos os combináveis ignoráveis. Tentar fazer isso pode levar a uma otimização prematura que introduz mais problemas do que corrige.
Há muitas situações em que ser ignorável não tem nenhum benefício real e pode levar a um código difícil de manter. Exemplo:
- Um elemento combinável que não é recomposto com frequência ou nunca.
- Um elemento combinável que chama apenas elementos combináveis que podem ser ignorados.
- Um elemento combinável com um grande número de parâmetros e implementações de igualdade caras. Nesse caso, o custo de verificar se algum parâmetro mudou pode ser maior do que o de uma recomposição barata.
Quando um elemento combinável pode ser ignorado, ele adiciona uma pequena sobrecarga que pode não valer a pena. Você pode até mesmo anotar seu elemento combinável como não reinicializável em casos em que você determina que ser reinicializável é mais sobrecarga do que vale a pena.