Quando você tiver uma classe instável que causa problemas de desempenho, precisará torná-la estável. Este documento descreve várias técnicas que podem ser usadas para fazer isso.
Ativar a rejeição avançada
Primeiro, tente ativar o modo de pulo forte. O modo de rejeição avançada 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 Pular com segurança para mais informações.
Tornar a classe imutável
Também é possível 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
val
em vez devar
e são de tipos imutáveis. - Tipos primitivos, como
String, Int
eFloat
, 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 reconhece se e quando o comportamento de métodos ou propriedades públicas do tipo vai gerar resultados diferentes de uma invocação anterior.
Coleções imutáveis
As coleções são um motivo comum pelo qual o Compose considera uma classe instável. 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 a coleções imutáveis do Kotlinx. Essas coleções são imutáveis, e o compilador do Compose as trata dessa forma. Essa biblioteca ainda está na versão Alfa, então podem ocorrer mudanças na API dela.
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
.
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. Ele é semelhante ao
operador !!
no Kotlin (link em inglês). É preciso ter muito
cuidado com o uso dessas anotações. Modificar o comportamento do compilador
pode levar a bugs imprevistos, como a recomposição do elemento combinável conforme
esperado.
Se for possível tornar a classe estável sem uma anotação, procure 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ê usa a anotação @Immutable
ou @Stable
, o compilador do Compose
marca 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ê anexe Snack
com @Immutable
, o compilador do Compose ainda marca
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 se trata de 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 seja sempre ignorável. Há vários caminhos a seguir.
Há várias maneiras de contornar o problema de 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,
pode considerar as coleções do Kotlin como estáveis adicionando
kotlin.collections.*
ao arquivo de configuração de estabilidade.
Coleção imutável
Para 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, você poderá criar a própria. Para fazer isso,
junte o List
em uma classe estável com anotação. Um wrapper genérico é provavelmente a
melhor escolha para isso, dependendo dos seus requisitos.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Em seguida, ele pode ser usado 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 usar qualquer uma dessas abordagens, o compilador do Compose agora marca 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 pular 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 considerados estáveis pode ser fornecido durante a 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. Comentários e caracteres curinga simples e duplos são aceitos. 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
bloco de opções composeCompiler
da configuração do plug-in do Gradle para 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. 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 faz referência estiverem explicitamente marcados 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 interface, que é a abordagem recomendada, talvez você encontre esse tipo de problema.
Solução
Para resolver esse problema, siga uma destas abordagens:
- Adicione as classes ao seu arquivo de configuração do compilador.
- Ative o compilador do Compose nos módulos da camada de dados ou marque as classes
com
@Stable
ou@Immutable
, quando apropriado.- Isso envolve adicionar uma dependência do Compose à camada de dados. No entanto,
ela é apenas a dependência do ambiente de execução do Compose, e não do
Compose-UI
.
- Isso envolve adicionar uma dependência do Compose à camada de dados. No entanto,
ela é apenas a dependência do ambiente de execução do Compose, e não do
- No módulo da interface, envolva as classes da camada de dados em classes de wrapper específicas da interface.
O mesmo problema também ocorre ao usar bibliotecas externas quando elas não usam o compilador do Compose.
Nem todos os elementos combináveis devem ser puláveis
Ao corrigir problemas de estabilidade, não tente pular todos os elementos combináveis. Tentar fazer isso pode levar a uma otimização prematura que apresenta mais problemas do que correções.
Há muitas situações em que ser pulável não tem nenhum benefício real e pode gerar um código difícil de manter. Exemplo:
- Um elemento combinável que não é recomposto com frequência ou que não é recomposto.
- Um elemento combinável que, por si só, chama elementos combináveis puláveis.
- Um elemento combinável com um grande número de parâmetros com implementações igualmente caras. Nesse caso, o custo de verificar se algum parâmetro foi alterado pode ser maior do que o custo de uma recomposição barata.
Quando um elemento combinável é pulável, 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 seja reiniciável nos casos em que você determinar que ser reiniciável gera mais sobrecarga do que vale a pena.