Corrigir problemas de estabilidade

Quando confrontada com uma classe instável que causa desempenho problemas, você deve torná-lo estável. Este documento descreve várias técnicas que você pode usar para isso.

Ativar a opção "Pular com bloqueio"

Primeiro, tente ativar o modo de pulo forte. Modo avançado de pular permite que elementos combináveis com parâmetros instáveis sejam ignorados e é o método mais fácil para corrigir problemas de desempenho causados pela estabilidade.

Consulte Ignorar 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 qualquer propriedade nunca pode mudar depois que uma instância desse tipo é construída, e todos os métodos são referencialmente transparente.
    • Verifique se todas as propriedades da classe são val em vez de var. e de tipos imutáveis.
    • Os tipos primitivos, como String, Int e Float, são sempre imutáveis.
    • Se isso for impossível, use o estado do Compose para propriedades mutáveis.
  • Estável: indica um tipo mutável. O ambiente de execução do Compose não tomar ciência se e quando qualquer propriedade pública ou método do tipo resultaria em 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 é possível ter certeza de que coleções como List, Map e Set estão são imutáveis e, portanto, os 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. Esses as coleções são imutáveis e o compilador do Compose as trata. como tal. Essa biblioteca ainda está na versão Alfa, então podem ocorrer mudanças na API dela.

Considere novamente esta classe instável do artigo Diagnosticar estabilidade de problemas:

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

É possível tornar tags estável usando uma coleção imutável. Na turma, mude 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 Compose o compilador marca a classe como estável.

Adicione as anotações Stable ou Immutable.

Um caminho possível para resolver problemas de estabilidade é anotar em classes instáveis com @Stable ou @Immutable.

Anotar uma classe substitui o que o compilador faria inferir sobre sua classe. É semelhante à Operador !! no Kotlin. Você deve ser muito cuidado com o uso dessas anotações. Como modificar o comportamento do compilador pode levar a bugs imprevistos, como a recomposição do elemento combinável ao como você espera.

Se for possível tornar sua classe estável sem uma anotação, você deve se esforçar para alcançar a estabilidade.

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 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 se você anotar Snack com @Immutable, o compilador do Compose ainda vai marcar o parâmetro snacks em HighlightedSnacks como instável.

Quando se trata de tipos de coleções, os parâmetros enfrentam o mesmo problema que as classes, 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 uma combinável para que seja sempre pulável. Há vários caminhos para frente.

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ê estiver feliz em cumprir o contrato de estabilidade em sua base de código, você pode considerar as coleções Kotlin como estáveis adicionando kotlin.collections.* para seu arquivo de configuração de estabilidade.

Coleção imutável

Para ter segurança de imutabilidade no momento da compilação, você pode 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, você poderá criar a própria. Para isso, encapsular o List em uma classe estável com anotação. Um wrapper genérico provavelmente a melhor opção para isso, dependendo de suas necessidades.

@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 uma dessas abordagens, o compilador do Compose agora marca a HighlightedSnacks combinável 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 de entrada mudaram.

Arquivo de configuração de estabilidade

A partir do Compose Compiler 1.5.5, um arquivo de configuração de classes para considerados estáveis podem ser fornecidos no tempo de compilação. Isso permite considerar classes que você não controla, como as da biblioteca padrão. como LocalDateTime, como estável.

O arquivo de configuração é um arquivo 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 o Compose do compilador.

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 em cada módulo do projeto separadamente, é possível fornecer configurações diferentes para módulos diferentes, se necessário. Como alternativa, tenha um de rede no nível raiz do seu projeto e transmita esse caminho a cada mais tarde neste módulo.

Vários módulos

Outro problema comum envolve a arquitetura com vários módulos. O compilador do Compose só podem inferir se uma classe é estável se todos os tipos não primitivos as referências são explicitamente marcadas como estáveis ou em um módulo que foi 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 é o recomendada, isso pode ser um problema que você encontra.

Solução

Para resolver esse problema, escolha uma das seguintes abordagens:

  1. Adicione as classes ao seu arquivo de configuração do compilador.
  2. Ativar o compilador do Compose nos módulos da camada de dados ou adicionar uma tag às classes com @Stable ou @Immutable, quando apropriado.
    • Isso envolve adicionar uma dependência do Compose à camada de dados. No entanto, isso depende apenas do ambiente de execução do Compose, não do Compose-UI:
  3. No módulo da interface, envolva as classes da camada de dados em um wrapper específico da interface classes.

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 devem ser puláveis

Ao trabalhar na correção de problemas de estabilidade, não tente fazer todas combinável e pulável. Tentar fazer isso pode levar a uma otimização prematura que introduz mais problemas do que corrige.

Há muitas situações em que ser pulável não tem nenhum benefício real e podem gerar códigos difíceis 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 custo igual a e implementações. Nesse caso, o custo de verificar se algum parâmetro tem mudou 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 reimplantá-lo. É possível até mesmo anotar que o elemento combinável é não reiniciável em casos em que você determina que ser reinicializável é maior do que vale a pena.