Quando si ha a che fare con una classe instabile che causa problemi di prestazioni, è necessario renderla stabile. Questo documento descrive diverse tecniche che puoi utilizzare per farlo.
Attivare lo strong skipping
Innanzitutto, prova ad attivare la modalità di strong skipping. La modalità di strong skipping consente di saltare gli elementi componibili con parametri instabili ed è il metodo più semplice per risolvere i problemi di prestazioni causati dalla stabilità.
Per ulteriori informazioni, consulta Strong skipping.
Rendere la classe immutabile
Puoi anche provare a rendere una classe instabile completamente immutabile.
- Immutabile: indica un tipo in cui il valore di qualsiasi proprietà non può mai
cambiare dopo la creazione di un'istanza di quel tipo e tutti i metodi sono
referenzialmente trasparenti.
- Assicurati che tutte le proprietà della classe siano sia
valanzichévarsia di tipi immutabili. - I tipi primitivi come
String, InteFloatsono sempre immutabili. - Se non è possibile, devi utilizzare lo stato di Compose per tutte le proprietà modificabili.
- Assicurati che tutte le proprietà della classe siano sia
- Stabile: indica un tipo modificabile. Il runtime di Compose non viene a conoscenza se e quando il comportamento di una proprietà pubblica o di un metodo del tipo produrrebbe risultati diversi da una chiamata precedente.
Raccolte immutabili
Un motivo comune per cui Compose considera una classe instabile sono le raccolte. Come indicato
nella pagina Diagnosticare i problemi di stabilità, il compilatore Compose
non può essere completamente sicuro che le raccolte come List, Map, e Set siano
veramente immutabili e quindi 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 progettate per essere immutabili e il compilatore Compose le tratta come tali. Questa libreria è ancora in versione alfa, quindi prevedi possibili modifiche alla sua API.
Considera di nuovo questa classe instabile dalla guida Diagnosticare i problemi di stabilità:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Puoi rendere tags stabile utilizzando una raccolta immutabile. Nella classe, modifica
il tipo di tags in ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
In questo modo, tutti i parametri della classe sono immutabili e il compilatore Compose contrassegna la classe come stabile.
Aggiungere l'annotazione Stable o Immutable
Un possibile modo per risolvere i problemi di stabilità è aggiungere l'annotazione @Stable o @Immutable alle classi instabili.
L'aggiunta di un'annotazione a una classe sostituisce ciò che il compilatore dedurrebbe altrimenti
sulla classe. È simile all'
!! operatore in Kotlin. Devi prestare molta attenzione a come utilizzi queste annotazioni. La sostituzione del comportamento del compilatore potrebbe causare bug imprevisti, ad esempio la mancata ricomposizione dell'elemento componibile quando te lo aspetti.
Se è possibile rendere stabile la classe senza un'annotazione, dovresti cercare di ottenere la stabilità in questo modo.
Lo snippet seguente fornisce un esempio minimo di una classe di dati con l'annotazione immutabile:
@Immutable
data class Snack(
…
)
Indipendentemente dall'utilizzo dell'annotazione @Immutable o @Stable, il compilatore Compose contrassegna la classe Snack come stabile.
Classi con annotazioni nelle raccolte
Considera un elemento componibile che include un parametro di tipo List<Snack>:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Anche se aggiungi l'annotazione @Immutable a Snack, il compilatore Compose contrassegna comunque
il parametro snacks in HighlightedSnacks come instabile.
I parametri presentano lo stesso problema delle classi quando si tratta di tipi di raccolta:
il compilatore Compose contrassegna sempre un parametro di tipo List come instabile, anche
quando si tratta di una raccolta di tipi stabili.
Non puoi contrassegnare un singolo parametro come stabile, né puoi aggiungere un'annotazione a un elemento componibile in modo che sia sempre ignorabile. Esistono più modi per procedere.
Esistono diversi modi per aggirare il problema delle raccolte instabili. Le sottosezioni seguenti descrivono questi diversi approcci.
File di configurazione
Se sei soddisfatto del contratto di stabilità nel tuo codebase, allora puoi
scegliere di considerare le raccolte Kotlin come stabili aggiungendo
kotlin.collections.* al tuo file
di configurazione della stabilità.
Raccolta immutabile
Per la sicurezza dell'immutabilità in tempo 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 tua. Per farlo, racchiudi List in una classe stabile con annotazioni. A seconda dei requisiti, la scelta migliore è probabilmente un wrapper generico.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Puoi quindi utilizzarlo come tipo del parametro nell'elemento componibile.
@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 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, Compose può ora saltare HighlightedSnacks se nessuno dei suoi input è cambiato.
File di configurazione della stabilità
A partire dal compilatore Compose 1.5.5, è possibile fornire un file di configurazione delle classi da considerare stabili in tempo di compilazione. In questo modo, le classi che non controlli, come le classi della libreria standard come LocalDateTime, possono essere considerate stabili.
Il file di configurazione è un file di testo con una classe per riga. Sono supportati commenti, caratteri jolly singoli e doppi.
Esempio di configurazione:
// 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
configurazione.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Poiché il compilatore Compose viene eseguito separatamente su ogni modulo del progetto, puoi fornire configurazioni diverse a moduli diversi, se necessario. In alternativa, puoi avere una configurazione a livello di root del progetto e passare il percorso a ogni modulo.
Più moduli
Un altro problema comune riguarda l'architettura multi-modulo. 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 anche con il compilatore Compose.
Se il livello dati si trova in un modulo separato dal livello UI, che è l'approccio consigliato, potresti riscontrare questo problema.
Soluzione
Per risolvere questo problema, puoi adottare uno dei seguenti approcci:
- Aggiungi le classi al file di configurazione del compilatore.
- Attiva il compilatore Compose sui moduli del livello dati o aggiungi l'annotazione
@Stableo@Immutablealle classi, se appropriato.- Ciò comporta l'aggiunta di una dipendenza di Compose al livello dati. Tuttavia, si tratta solo della dipendenza per il runtime di Compose e non per
Compose-UI.
- Ciò comporta l'aggiunta di una dipendenza di Compose al livello dati. Tuttavia, si tratta solo della dipendenza per il runtime di Compose e non per
- All'interno del modulo UI, racchiudi le classi del livello dati in classi wrapper specifiche dell'UI.
Lo stesso problema si verifica anche quando si utilizzano librerie esterne se non utilizzano il compilatore Compose.
Non tutti gli elementi componibili devono essere ignorabili
Quando lavori per risolvere i problemi di stabilità, non devi tentare di rendere ignorabile ogni elemento componibile. Se provi a farlo, potresti ottenere un'ottimizzazione prematura che introduce più problemi di quelli che risolve.
Esistono molte situazioni in cui l'ignorabilità non comporta alcun vantaggio reale e può portare a un codice difficile da gestire. Ad esempio:
- Un elemento componibile che non viene ricomposto spesso o mai.
- Un elemento componibile che di per sé chiama solo elementi componibili ignorabili.
- Un elemento componibile con un numero elevato di parametri con implementazioni di uguaglianza costose. In questo caso, il costo del controllo della modifica di un parametro potrebbe superare il costo di una ricomposizione economica.
Quando un elemento componibile è ignorabile, aggiunge un piccolo overhead che potrebbe non valere la pena. Puoi anche aggiungere un'annotazione all'elemento componibile in modo che non sia riavviabile nei casi in cui determini che la riavviabilità comporta un overhead maggiore del suo valore.