Quando ti trovi di fronte a una classe instabile che causa problemi di prestazioni, dovresti renderla stabile. Questo documento descrive varie tecniche che puoi utilizzare per ottenere questo risultato.
Rendi immutabile il corso
Prima di tutto, dovresti 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 trasparenti a livello di riferimento.
- Assicurati che tutte le proprietà della classe siano
val
anzichévar
e che siano di tipo immutabile. - I tipi primitivi come
String, Int
eFloat
sono sempre immutabili. - Se questo non è possibile, devi utilizzare lo stato Scrivi per tutte le proprietà modificabili.
- Assicurati che tutte le proprietà della classe siano
- Stabile: indica un tipo modificabile. Il runtime di Compose non rileva se e quando una delle proprietà pubbliche o il comportamento del metodo di un tipo restituisce risultati diversi rispetto a una chiamata precedente.
Raccolte immutabili
Un motivo comune per cui Compose considera instabile un corso sono le raccolte. Come indicato nella pagina Diagnostica problemi di stabilità, il compilatore di Compose non può essere sicuro che raccolte come List, Map
e Set
siano realmente immutabili e pertanto le contrassegna come instabili.
Per risolvere il problema, puoi utilizzare le raccolte immutabili. Il compilatore Compose include il supporto per le raccolte immutabili di Kotlinx. Queste raccolte sono garantite come immutabili e il compilatore di Compose le considera come tali. Questa libreria è ancora in versione alpha, quindi aspettati possibili modifiche alla relativa API.
Considera di nuovo questa classe instabile dalla guida Diagnostica problemi di stabilità:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Puoi rendere stabile tags
utilizzando una raccolta immutabile. Nel corso, cambia il tipo di tags
in ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
In seguito, tutti i parametri della classe saranno immutabili e il compilatore di Compose contrassegna la classe come stabile.
Annota 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 il compilatore
sarebbe altrimenti dedotto dalla tua classe. È simile all'operatore
!!
di Kotlin. Dovresti fare molta attenzione
a come usi queste annotazioni. La sostituzione del comportamento del compilatore potrebbe comportare bug imprevisti, ad esempio la mancata ricomposizione della componibile quando previsto.
Se è possibile rendere stabile la tua classe senza un'annotazione, devi sforzarti di raggiungere la stabilità in questo modo.
Lo snippet seguente fornisce un esempio minimo di una classe di dati annotata come immutabile:
@Immutable
data class Snack(
…
)
A prescindere dal fatto che utilizzi l'annotazione @Immutable
o @Stable
, il compilatore di Compose contrassegna la classe Snack
come stabile.
Classi con annotazioni nelle raccolte
Considera un oggetto componibile che include un parametro di tipo List<Snack>
:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Anche se annotati Snack
con @Immutable
, il compilatore Compose contrassegna comunque
il parametro snacks
in HighlightedSnacks
come instabile.
Per quanto riguarda i tipi di raccolta, i parametri devono affrontare lo stesso problema delle classi.
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é annotare un elemento componibile in modo che sia sempre ignorabile. Esistono più percorsi in avanti.
Esistono diversi modi per risolvere il problema dell'instabilità delle raccolte. Le seguenti sottosezioni illustrano questi diversi approcci.
File di configurazione
Se vuoi rispettare il contratto di stabilità nel tuo codebase, puoi scegliere di considerare stabili le raccolte Kotlin aggiungendo kotlin.collections.*
al tuo file di configurazione della stabilità.
Raccolta immutabile
Per garantire 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 personalizzata. Per farlo,
aggrega List
in una classe stabile annotata. A seconda dei requisiti, la scelta migliore
è probabilmente un wrapper generico.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Puoi quindi utilizzarlo come tipo di parametro nella 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 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 Scrivi può saltare HighlightedSnacks
se nessuno dei suoi input è stato modificato.
File di configurazione della stabilità
A partire dalla versione 1.5.5 di Compose Compiler, è possibile fornire un file di configurazione di classi da considerare stabili al momento della compilazione. In questo modo, puoi considerare stabili le classi che non controlli, come le classi di librerie 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. Di seguito è riportata una configurazione di esempio:
// 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<*,_>
Per abilitare questa funzionalità, passa il percorso del file di configurazione alle opzioni del compilatore di Scrivi.
Trendy
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"
)
}
Poiché il compilatore di Compose viene eseguito separatamente su ogni modulo del progetto, puoi fornire configurazioni diverse a moduli diversi, se necessario. In alternativa, imposta una configurazione a livello della directory principale del progetto e passa il percorso a ciascun modulo.
Più moduli
Un altro problema comune riguarda un'architettura multimodulo. Il compilatore Compose può dedurre solo se una classe è stabile 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 dell'interfaccia utente, che è l'approccio consigliato, potrebbe trattarsi di un problema.
Soluzione
Per risolvere il problema, puoi adottare uno dei seguenti approcci:
- Aggiungi le classi al file di configurazione del compilatore.
- Abilita il compilatore Compose nei moduli del livello dati o tagga le tue classi con
@Stable
o@Immutable
dove appropriato.- Ciò comporta l'aggiunta di una dipendenza 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 Compose al livello dati. Tuttavia, si tratta solo della dipendenza per il runtime di Compose e non per
- Nel modulo UI, aggrega le classi del livello dati in classi wrapper specifiche dell'interfaccia utente.
Lo stesso problema si verifica anche quando si utilizzano librerie esterne se non usano il compilatore Composer.
Non tutti i prodotti componibili devono essere ignorabili
Quando cerchi di risolvere problemi di stabilità, non devi cercare di rendere ignorabile ogni elemento componibile. Tentare di farlo può portare a un'ottimizzazione prematura che introduce più problemi di quanti ne consenta la correzione.
Esistono molte situazioni in cui l'ignoramento non offre alcun reale vantaggio e può portare a una gestione del codice complessa. Ecco alcuni esempi:
- Un prodotto componibile che non viene ricomposto spesso o per niente.
- Un elemento componibile che di per sé chiama elementi componibili ignorabili.
- Un componibile con un gran numero di parametri con costose implementazioni di tipo uguale. In questo caso, il costo di verificare la modifica di un parametro può superare il costo di una ricomposizione economica.
Quando un annuncio componibile è ignorabile, aggiunge un piccolo overhead che potrebbe non valere la pena. Puoi persino annotare la tua componibile in modo che sia non riavviabile nei casi in cui stabilisca che essere riavviabile comporta un overhead maggiore del suo valore.