Quando ti trovi di fronte a una classe instabile che causa problemi di prestazioni, devi renderla stabile. Questo documento descrive diverse tecniche che puoi utilizzare per farlo.
Attiva l'interruzione decisa
Ti consigliamo di provare prima 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 dall'instabilità.
Per saperne di più, consulta Salto forzato.
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évar, sia di tipi immutabili. - I tipi primitivi come
String, InteFloatsono sempre immutabili. - Se questo non è possibile, devi utilizzare lo stato di Compose per qualsiasi proprietà modificabile.
- Assicurati che tutte le proprietà della classe siano sia
- Stabile: indica un tipo modificabile. Il runtime di Compose non si accorge se e quando una delle proprietà o dei metodi pubblici 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 problemi di stabilità, il compilatore Compose
non può essere completamente certo che raccolte come List, Map e Set siano
veramente immutabili e pertanto le contrassegna come instabili.
Per risolvere il problema, puoi utilizzare le raccolte immutabili. Il compilatore Compose include il supporto per Kotlinx Immutable Collections. Queste raccolte sono garantite come immutabili e il compilatore Compose le tratta come tali. Questa libreria è ancora in versione alpha, quindi prevedi possibili modifiche alla sua API.
Considera di nuovo questa classe instabile della guida Diagnostica i problemi di stabilità:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Puoi rendere tags stabile utilizzando una raccolta immutabile. Nella classe, cambia
il tipo di tags in ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
In questo modo, tutti i parametri della classe diventano immutabili e il compilatore Compose contrassegna la classe come stabile.
Aggiungere 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 esegue l'override di ciò che il compilatore altrimenti
dedurrebbe sulla classe. È simile all'operatore !! in Kotlin. Devi fare molta attenzione
a come utilizzi queste annotazioni. L'override del comportamento del compilatore
potrebbe causare bug imprevisti, ad esempio la mancata ricomposizione del composable quando
ti aspetti che lo faccia.
Se è possibile rendere stabile la classe senza un'annotazione, dovresti cercare di ottenere la stabilità in questo modo.
Il seguente snippet fornisce un esempio minimo di una classe di dati annotata come immutabile:
@Immutable
data class Snack(
…
)
Indipendentemente dal fatto che utilizzi l'annotazione @Immutable o @Stable, il compilatore Compose
contrassegna la classe Snack come stabile.
Classi annotate nelle raccolte
Prendi in considerazione un composable che includa 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 Compose contrassegna comunque
il parametro snacks in HighlightedSnacks come instabile.
I parametri presentano lo stesso problema delle classi per quanto riguarda i tipi di raccolta,
il compilatore 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é puoi annotare un componente componibile in modo che sia sempre ignorabile. Esistono più percorsi da seguire.
Esistono diversi modi per risolvere il problema delle raccolte instabili. Le seguenti sottosezioni descrivono in dettaglio questi diversi approcci.
File di configurazione
Se accetti di rispettare il contratto di stabilità nel tuo codebase, puoi attivare la considerazione delle raccolte Kotlin come stabili aggiungendo kotlin.collections.* al file di configurazione della stabilità.
Raccolta immutabile
Per la sicurezza in fase di compilazione dell'immutabilità, 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,
inserisci List in una classe stabile annotata. Un wrapper generico è probabilmente la
scelta migliore, a seconda dei tuoi requisiti.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Puoi quindi utilizzarlo come tipo di parametro nel tuo 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 il
composabile 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 ora può ignorare HighlightedSnacks se nessuno dei suoi
input è cambiato.
File di configurazione della stabilità
A partire da Compose Compiler 1.5.5, è possibile fornire un file di configurazione delle classi da considerare stabili in fase di compilazione. Ciò consente di considerare stabili le classi che non controlli, ad esempio le classi della 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 blocco
di opzioni composeCompiler della configurazione del plug-in Gradle del compilatore Compose.
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 radice del progetto e passare questo 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 si trovano in un modulo compilato anch'esso con il compilatore Compose.
Se il livello dati si trova in un modulo separato dal livello UI, che è l'approccio consigliato, questo potrebbe essere un problema che riscontri.
Soluzione
Per risolvere il 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 tagga le classi
con
@Stableo@Immutable, a seconda dei casi.- Ciò comporta l'aggiunta di una dipendenza Compose al livello dati. Tuttavia,
è solo la dipendenza per il runtime di Compose e non per
Compose-UI.
- Ciò comporta l'aggiunta di una dipendenza Compose al livello dati. Tuttavia,
è solo la dipendenza per il runtime di Compose e non per
- All'interno del modulo UI, racchiudi le classi del livello dati in classi wrapper specifiche per la UI.
Lo stesso problema si verifica anche quando si utilizzano librerie esterne se non utilizzano il compilatore Compose.
Non tutti i composable devono essere ignorabili
Quando lavori per risolvere i problemi di stabilità, non devi tentare di rendere ogni componente componibile ignorabile. Se provi a farlo, potresti ottenere un'ottimizzazione prematura che introduce più problemi di quanti ne risolve.
Esistono molte situazioni in cui la possibilità di saltare non offre alcun vantaggio reale e può portare a un codice difficile da gestire. Ad esempio:
- Un composable che non viene ricomposto spesso o mai.
- Un componente componibile che chiama solo componenti componibili ignorabili.
- Un composable con un numero elevato di parametri con implementazioni equals costose. In questo caso, il costo del controllo di eventuali modifiche ai parametri potrebbe superare il costo di una ricomposizione economica.
Quando un elemento componibile è ignorabile, aggiunge un piccolo overhead che potrebbe non valerne la pena. Puoi anche annotare il tuo elemento componibile come non riavviabile nei casi in cui ritieni che la riavviabilità comporti un overhead maggiore di quanto valga.