Quando hai a che fare con un modulo instabile che causa problemi di prestazioni, devi renderlo stabile. Questo documento illustra varie tecniche che puoi usare a questo scopo.
Attiva l'opzione Ignora forte
Prima di tutto dovresti provare ad attivare la modalità di salto forte. La modalità di salto forzato consente di saltare i composabili con parametri instabili ed è il metodo più semplice per risolvere i problemi di prestazioni causati dalla stabilità.
Per ulteriori informazioni, consulta Salto di segmenti.
Rendi il corso immutabile
Puoi anche provare a rendere una classe instabile del tutto immutabile.
- Immutabile: indica un tipo in cui il valore di qualsiasi proprietà non può mai essere modificato dopo la creazione di un'istanza di quel tipo e tutti i metodi sono trasparenti dal punto di vista referenziale.
- Assicurati che tutte le proprietà della classe siano
val
anzichévar
e di tipi immutabili. - I tipi primitivi come
String, Int
eFloat
sono sempre immutabili. - Se ciò non è possibile, devi utilizzare lo stato di composizione per tutte le proprietà mutevoli.
- Assicurati che tutte le proprietà della classe siano
- Stabile: indica un tipo mutabile. Il runtime di Compose non sa se e quando qualsiasi proprietà pubblica o comportamento del metodo del tipo potrebbe restituire risultati diversi rispetto a una chiamata precedente.
Raccolte immutabili
Un motivo comune per cui Compose considera una classe instabile sono le raccolte. Come indicato nella pagina Diagnostica i problemi di stabilità, il compilatore Compose non può essere completamente sicuro che le raccolte come List, Map
e Set
siano davvero immutabili e pertanto le contrassegna come instabili.
Per risolvere il problema, puoi utilizzare raccolte immutabili. Il compilatore Compose include il supporto per le raccolte immutabili Kotlinx. Queste raccolte sono garantite come immutabili e il compilatore Compose le considera come tali. Questa libreria è ancora in versione alpha, quindi aspettati possibili modifiche all'API.
Considera di nuovo questa classe instabile della guida Diagnosticare i problemi di stabilità:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Puoi rendere stabile tags
utilizzando una raccolta immutabile. Nella classe, cambia il tipo di tags
in ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Dopo aver eseguito questa operazione, tutti i parametri della classe sono immutabili e il compilatore di Compose contrassegna la classe come stabile.
Aggiungi annotazioni con Stable
o Immutable
Un possibile percorso per risolvere i problemi di stabilità è annotare le classi instabili con @Stable
o @Immutable
.
L'annotazione di una classe sostituisce ciò che altrimenti il compilatore inferirebbe sulla classe. È simile all'operatore !!
in Kotlin. Devi fare molta attenzione a come utilizzi queste annotazioni. L'override del comportamento del compilatore potrebbe portare a bug imprevisti, ad esempio la mancata ricompozione del composable quando previsto.
Se è possibile rendere stabile la classe senza un'annotazione, dovresti sforzarti di raggiungere la stabilità in questo modo.
Lo snippet seguente fornisce un esempio minimo di classe di dati annotata come immutabile:
@Immutable
data class Snack(
…
)
Indipendentemente dall'annotazione @Immutable
o @Stable
utilizzata, il compilatore Compose contrassegnerà la classe Snack
come stabile.
Classi annotate nelle raccolte
Considera un componibile che include un parametro di tipo List<Snack>
:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Anche se annoti Snack
con @Immutable
, il compilatore di Compose contrassegna comunque il parametro snacks
in HighlightedSnacks
come instabile.
I parametri presentano lo stesso problema delle classi per quanto riguarda i tipi di raccolte:
il compilatore di Compose contrassegna sempre un parametro di tipo List
come instabile, anche
se si tratta di una raccolta di tipi stabili.
Non puoi contrassegnare un singolo parametro come stabile, né annotare un elemento componibile in modo che sia sempre ignorabile. Esistono più percorsi possibili.
Esistono diversi modi per risolvere il problema delle raccolte instabili. Le seguenti sottosezioni descrivono questi diversi approcci.
File di configurazione
Se accetti di rispettare il contratto di stabilità nella tua base di codice, puoi attivare la considerazione delle raccolte Kotlin come stabili aggiungendo kotlin.collections.*
al file di configurazione della stabilità.
Raccolta immutabile
Per la sicurezza dell'immutabilità in fase di compilazione, puoi utilizzare una raccolta immutabile kotlinx anziché List
.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Se non puoi utilizzare una raccolta immutabile, puoi crearne una. Per farlo,
aggrega List
in una classe stabile annotata. Un wrapper generico è probabilmente la scelta migliore per questo, a seconda dei tuoi requisiti.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
che potrai utilizzare come tipo di parametro nel composable.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Soluzione
Dopo aver adottato uno di questi approcci, il compilatore Compose ora contrassegna l'elemento componibile HighlightedSnacks
come 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 la ricomposizione, ora Compose può saltare HighlightedSnacks
se nessuno dei suoi
input è cambiato.
File di configurazione della stabilità
A partire da Compose Compiler 1.5.5, al momento della compilazione può essere fornito un file di configurazione di classi da considerare stabili. Ciò consente di considerare stabili le classi che non controlli, come le classi di libreria standard come LocalDateTime
.
Il file di configurazione è un file di testo normale con una classe per riga. Sono supportati commenti, caratteri jolly singoli e doppi. Ecco una configurazione di esempio:
// 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<*,_>
Per attivare questa funzionalità, passa il percorso del file di configurazione al
composeCompiler
blocco di opzioni del plug-in Gradle del compilatore Compose.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Poiché il compilatore di Compose viene eseguito separatamente su ogni modulo del progetto, se necessario puoi fornire configurazioni diverse a moduli diversi. In alternativa, avere una configurazione a livello principale del progetto e passare il percorso a ogni modulo.
Più moduli
Un altro problema comune riguarda l'architettura multimodulo. Il compilatore Compose può dedurre se una classe è stabile solo se tutti i tipi non primitivi a cui fa riferimento sono contrassegnati esplicitamente come stabili o in un modulo creato anch'esso con il compilatore Compose.
Se il livello dati si trova in un modulo separato dal livello UI, che è l'approccio consigliato, potresti riscontrare un problema.
Soluzione
Per risolvere il problema, puoi adottare uno dei seguenti approcci:
- Aggiungi i corsi al file di configurazione del compilatore.
- Attiva il compilatore Compose nei moduli del livello dati o contrassegna le classi con
@Stable
o@Immutable
, se opportuno.- Ciò comporta l'aggiunta di una dipendenza da Compose al livello di dati. Tuttavia,
si tratta solo della dipendenza per il runtime di Compose e non per
Compose-UI
.
- Ciò comporta l'aggiunta di una dipendenza da Compose al livello di dati. Tuttavia,
si tratta solo della dipendenza per il runtime di Compose e non per
- All'interno del modulo dell'interfaccia utente, inserisci le classi del livello di dati in classi wrapper specifiche per l'interfaccia utente.
Lo stesso problema si verifica anche quando si utilizzano librerie esterne se non usano il compilatore Composer.
Non tutti i componibili devono essere ignorabili
Quando cerchi di risolvere i problemi di stabilità, non dovresti tentare di rendere ogni componente ignorabile. Il tentativo di farlo può portare a un'ottimizzazione prematura che introduce più problemi di quanti ne risolva.
In molti casi gli annunci ignorabili non apportano alcun vantaggio reale e possono complicare la manutenzione del codice. Ad esempio:
- Un componibile che non viene ricomposto spesso o affatto.
- Un componibile che di per sé chiama semplicemente elementi componibili ignorabili.
- Un componibile con un gran numero di parametri con implementazioni di tipo "Equals" (costose). In questo caso, il costo del controllo dell'eventuale variazione di un parametro potrebbe superare il costo di una ricostituzione economica.
Quando un composable è ignorabile, aggiunge un piccolo overhead che potrebbe non essere utile. Puoi persino annotare il tuo componibile in modo che sia non riavviabile nei casi in cui determini che il riavvio è più overhead di quanto valga.