Quando você se depara com uma classe instável que causa problemas de desempenho, precisa torná-la estável. Este documento descreve várias técnicas que podem ser usadas para fazer isso.
Ativar ação "pular" com segurança
Primeiro, tente ativar o modo forte de pular. O modo forte para ignorar 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 Pule forte 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 uma 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. - Os tipos primitivos, como
String, Int
eFloat
, são sempre imutáveis. - Se isso for impossível, use o estado do Compose para qualquer propriedade mutável.
- Verifique se todas as propriedades da classe são
- Stable: indica um tipo mutável. O ambiente de execução do Compose não sabe se e quando qualquer propriedade pública ou comportamento do método do tipo geraria resultados diferentes de uma invocação anterior.
Coleções imutáveis
As coleções são um motivo comum para o Compose considerar uma classe instável. 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, você pode usar coleções imutáveis. O compilador do Compose inclui suporte a Coleções imutáveis 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 haver 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>
…
}
É possível tornar tags
estável usando uma coleção imutável. Na classe, altere
o tipo de tags
para ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Depois disso, todos os parâmetros da classe ficarão imutáveis, e o compilador do Compose a marcará 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. Ele é semelhante ao
operador !!
no Kotlin (link em inglês). Você precisa ter muito
cuidado com o uso dessas anotações. Substituir o comportamento do compilador
pode causar bugs imprevistos, por exemplo, o elemento combinável não é recomposto conforme
o esperado.
Se for possível tornar sua classe estável sem uma anotação, tente obter estabilidade dessa maneira.
O snippet abaixo fornece um exemplo mínimo de uma classe de dados anotada como imutável:
@Immutable
data class Snack(
…
)
Independentemente de você usar a anotação @Immutable
ou @Stable
, o compilador do Compose
marca a classe Snack
como estável.
Classes com anotações 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ê anota 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 das 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 elemento combinável para que seja sempre pulável. Há vários caminhos a seguir.
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 cumprir o contrato de estabilidade na sua base de código, poderá
optar por 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 garantir a segurança da imutabilidade no momento de compilação, é possível
usar 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,
una a 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>
)
Você pode usar 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 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
No Compose Compiler 1.5.5 e versões mais recentes, um arquivo de configuração de classes a serem
consideradas estáveis pode ser fornecido durante a compilação. Isso permite considerar
como classes que você não controla, como classes de biblioteca padrão,
como LocalDateTime
, como estáveis.
O arquivo de configuração é de texto simples com uma classe por linha. Comentários, caracteres curinga simples e duplos são aceitos. Confira abaixo um exemplo de configuração:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider kotlin collections stable
kotlin.collections.*
// 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 as opções do compilador do Compose.
Groovy
kotlinOptions {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
project.absolutePath + "/compose_compiler_config.conf"
]
}
Kotlin
kotlinOptions {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
"${project.absolutePath}/compose_compiler_config.conf"
)
}
Como o compilador do Compose é executado separadamente em cada módulo do projeto, você pode fornecer configurações diferentes para módulos diferentes, se necessário. Como alternativa, defina 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 com vários módulos. O compilador do Compose só poderá inferir se uma classe é estável se todos os tipos não primitivos a que ele faz referência forem 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, esse pode ser um problema.
Solução
Para resolver esse problema, você pode adotar 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
@Stable
ou@Immutable
, quando apropriado.- Isso envolve adicionar 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 adicionar 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 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, caso elas não usem o compilador do Compose.
Nem todos os elementos combináveis podem ser puláveis
Ao corrigir problemas de estabilidade, não tente tornar todos os elementos puláveis puláveis. Essa tentativa pode levar a uma otimização prematura que apresenta mais problemas do que corrige.
Há muitas situações em que a ação de pular não traz benefícios reais e pode levar à dificuldade de manter o código. Por exemplo:
- Um elemento combinável que não é recomposto com frequência ou que não é recomposto com frequência.
- 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 iguais caras. Nesse caso, o custo de verificar se algum parâmetro foi alterado pode superar 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. Também é possível definir que o elemento combinável seja não reiniciável nos casos em que você determina que essa reinicialização representa mais sobrecarga do que vale a pena.