Quando você se deparar com uma classe instável que causa problemas de desempenho, torne-a estável. Este documento descreve várias técnicas que podem ser usadas para isso.
Ativar a rejeição avançada
Primeiro, tente ativar o modo de rejeição avançada. Esse modo permite que elementos combináveis com parâmetros instáveis sejam ignorados e é o método mais fácil de corrigir problemas de desempenho causados pela estabilidade.
Consulte Rejeição avançada 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 pode
mudar 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 for impossível, use o estado do Compose para todas as propriedades mutáveis.
- Verifique se todas as propriedades da classe são
- Estável: indica um tipo mutável. O ambiente de execução do Compose não fica ciente se e quando alguma das propriedades públicas ou o comportamento do método do tipo produziria 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 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 coleções imutáveis do Kotlinx. Essas coleções são projetadas para serem imutáveis, e o compilador do Compose as trata como tal. Essa biblioteca ainda está na versão Alfa. Portanto, espere possíveis mudanças na API.
Considere novamente essa classe instável do guia Diagnosticar problemas de estabilidade:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Você pode 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 serão imutáveis, e o compilador do Compose vai marcar a classe como estável.
Anotar com Stable ou Immutable
Um caminho possível para 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.
infer É semelhante ao
!! operador no Kotlin. Tenha muito cuidado ao usar essas anotações. A substituição do comportamento do compilador pode levar a bugs imprevistos, como o elemento combinável não ser recomposto quando esperado.
Se for possível tornar sua classe estável sem uma anotação, tente alcançar a estabilidade dessa forma.
O snippet a seguir fornece um exemplo mínimo de uma classe de dados anotada como imutável:
@Immutable
data class Snack(
…
)
Se você usar a anotação @Immutable ou @Stable, o compilador do Compose vai marcar a classe Snack como estável.
Classes anotadas em coleções
Considere um elemento combinável que inclua 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 elemento combinável para que ele sempre possa ser ignorado. Há vários caminhos para frente.
Há várias maneiras de contornar o problema de coleções instáveis. As subseções a seguir descrevem essas diferentes abordagens.
Arquivo de configuração
Se você quiser obedecer ao contrato de estabilidade na sua base de código, então você poderá aceitar considerar as coleções do Kotlin como estáveis adicionando kotlin.collections.* ao seu arquivo de configuração de estabilidade.
Coleção imutável
Para a segurança de imutabilidade em 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 você não puder usar uma coleção imutável, crie uma. Para fazer isso, envolva a List em uma classe estável anotada. Um wrapper genérico provavelmente é a melhor opção 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 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, um arquivo de configuração de classes a serem consideradas estáveis pode ser fornecido no tempo de compilação. Isso permite considerar classes que você não controla, como classes de biblioteca padrão como LocalDateTime, como estáveis.
O arquivo de configuração é um arquivo de texto simples com uma classe por linha. Há suporte para comentários e caracteres curinga únicos e duplos.
Um 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
composeCompiler bloco de opções do plug-in do Gradle do compilador do Compose
configuração.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Como o compilador do Compose é executado em cada módulo do seu projeto separadamente, você pode fornecer configurações diferentes para módulos diferentes, se necessário. Como alternativa, tenha uma configuração no nível raiz do projeto e transmita 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 a que ela se refere forem marcados explicitamente como estáveis ou 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 IU, que é a abordagem recomendada, esse poderá ser um problema.
Solução
Para resolver esse problema, adote uma das seguintes 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 as classes com
@Stableou@Immutable, conforme apropriado.- Isso envolve a adição de uma dependência do Compose à camada de dados. No entanto, é apenas a dependência do ambiente de execução do Compose e não do
Compose-UI.
- Isso envolve a adição de uma dependência do Compose à camada de dados. No entanto, é apenas a dependência do ambiente de execução do Compose e não do
- No módulo de IU, envolva as classes da camada de dados em classes de wrapper específicas da IU.
O mesmo problema também ocorre ao usar bibliotecas externas se elas não usarem o compilador do Compose.
Nem todos os elementos combináveis precisam ser ignoráveis
Ao trabalhar para corrigir problemas de estabilidade, não tente tornar todos os elementos 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, por si só, apenas chama elementos combináveis ignoráveis.
- Um elemento combinável com um grande número de parâmetros com implementações iguais caras. Nesse caso, o custo de verificar se algum parâmetro mudou pode superar o custo 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 o elemento combinável para que ele não possa ser reiniciado nos casos em que determinar que ser reiniciável é mais sobrecarga do que vale a pena.