In questa pagina scoprirai il ciclo di vita di un elemento componibile e come Compose decide se è necessaria una ricomposizione.
Panoramica del ciclo di vita
Come indicato nella documentazione sulla gestione dello stato, una composizione descrive l'UI della tua app ed è il risultato dell'esecuzione di elementi componibili. Una composizione è una struttura ad albero degli elementi componibili che descrivono la tua UI.
Quando Jetpack Compose esegue gli elementi componibili per la prima volta, durante la composizione iniziale, tiene traccia degli elementi componibili che chiami per descrivere la tua UI in una composizione. Poi, quando lo stato della tua app cambia, Jetpack Compose pianifica una ricomposizione. La ricomposizione si verifica quando Jetpack Compose riesegue gli elementi componibili che potrebbero essere cambiati in risposta alle modifiche dello stato e poi aggiorna la composizione per riflettere le modifiche.
Una composizione può essere prodotta solo da una composizione iniziale e aggiornata da una ricomposizione. L'unico modo per modificare una composizione è tramite la ricomposizione.
In genere, la ricomposizione viene attivata da una modifica a un
State<T> oggetto. Compose
tiene traccia di questi elementi ed esegue tutti gli elementi componibili nella composizione che leggono quel
particolare State<T>, e tutti gli elementi componibili che chiamano e che non possono essere
ignorati.
Se un elemento componibile viene chiamato più volte, nella composizione vengono inserite più istanze. Ogni chiamata ha il suo ciclo di vita nella composizione.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable nella composizione. Se un elemento componibile viene chiamato più volte, nella composizione vengono inserite più istanze. Un elemento di un colore diverso indica che si tratta di un'istanza separata.Anatomia di un elemento componibile nella composizione
L'istanza di un elemento componibile nella composizione è identificata dal suo sito di chiamata. Il compilatore Compose considera ogni sito di chiamata come distinto. La chiamata di elementi componibili da più siti di chiamata creerà più istanze dell'elemento componibile nella composizione.
Se durante una ricomposizione un elemento componibile chiama elementi componibili diversi da quelli chiamati durante la composizione precedente, Compose identificherà gli elementi componibili chiamati o non chiamati e, per gli elementi componibili chiamati in entrambe le composizioni, Compose eviterà di ricomporli se i relativi input non sono cambiati.
La conservazione dell'identità è fondamentale per associare gli effetti collaterali al relativo elemento componibile, in modo che possano essere completati correttamente anziché riavviati per ogni ricomposizione.
Considera l'esempio seguente:
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
Nello snippet di codice riportato sopra, LoginScreen chiamerà in modo condizionale l'elemento componibile LoginError e chiamerà sempre l'elemento componibile LoginInput. Ogni chiamata ha un sito di chiamata e una posizione di origine univoci, che il compilatore utilizzerà per identificarla in modo univoco.
LoginScreen nella composizione quando lo stato cambia e si verifica una ricomposizione. Lo stesso colore indica che l'elemento non è stato ricomposto.Anche se LoginInput è passato dall'essere chiamato per primo all'essere chiamato per secondo, l'istanza LoginInput verrà conservata tra le ricomposizioni. Inoltre, poiché LoginInput non ha parametri che sono cambiati durante la ricomposizione, la chiamata a LoginInput verrà ignorata da Compose.
Aggiungere informazioni aggiuntive per facilitare le ricomposizioni intelligenti
La chiamata di un elemento componibile più volte lo aggiungerà anche alla composizione più volte. Quando chiami un elemento componibile più volte dallo stesso sito di chiamata, Compose non ha informazioni per identificare in modo univoco ogni chiamata a quell'elemento componibile, quindi l'ordine di esecuzione viene utilizzato in aggiunta al sito di chiamata per mantenere distinte le istanze. A volte questo comportamento è tutto ciò che serve, ma in alcuni casi può causare un comportamento indesiderato.
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
Nell'esempio riportato sopra, Compose utilizza l'ordine di esecuzione in aggiunta al sito di chiamata per mantenere distinta l'istanza nella composizione. Se un nuovo movie viene aggiunto alla fine dell'elenco, Compose può riutilizzare le istanze già presenti nella composizione, poiché la loro posizione nell'elenco non è cambiata e, di conseguenza, l'input movie è lo stesso per queste istanze.
MoviesScreen nella composizione quando un nuovo elemento viene aggiunto alla fine dell'elenco. Gli elementi componibili MovieOverview nella composizione possono essere riutilizzati. Lo stesso colore in MovieOverview indica che l'elemento componibile non è stato ricomposto.Tuttavia, se l'elenco movies cambia aggiungendo elementi all'inizio o al centro dell'elenco, rimuovendo o riordinando gli elementi, si verificherà una ricomposizione in tutte le chiamate MovieOverview il cui parametro di input ha cambiato posizione nell'elenco. Questo è estremamente importante se, ad esempio, MovieOverview recupera l'immagine di un film utilizzando un effetto collaterale. Se la ricomposizione avviene mentre l'effetto è in corso, verrà annullata e riavviata.
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }
MoviesScreen nella composizione quando un nuovo elemento viene aggiunto all'elenco. Gli elementi componibili MovieOverview non possono essere riutilizzati e tutti gli effetti collaterali verranno riavviati. Un colore diverso in MovieOverview indica che l'elemento componibile è stato ricomposto.Idealmente, vogliamo considerare l'identità dell'istanza MovieOverview come collegata all'identità di movie che le viene passata. Se riordiniamo l'elenco dei film, idealmente riordineremmo anche le istanze nell'albero della composizione anziché ricomporre ogni elemento componibile MovieOverview con un'istanza di film diversa. Compose ti consente di indicare al runtime
i valori che vuoi utilizzare per identificare una determinata parte dell'albero: l'
key
elemento componibile.
Se racchiudi un blocco di codice con una chiamata all'elemento componibile key con uno o più valori passati, questi valori verranno combinati per identificare l'istanza nella composizione. Il valore di una key non deve essere univoco a livello globale, ma solo tra le invocazioni degli elementi componibili nel sito di chiamata. Quindi, in questo esempio, ogni movie deve avere una
key univoca tra i movies; non è un problema se condivide la key con
un altro elemento componibile in un'altra parte dell'app.
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
Con quanto riportato sopra, anche se gli elementi dell'elenco cambiano, Compose riconosce le singole chiamate a MovieOverview e può riutilizzarle.
MoviesScreen nella composizione quando un nuovo elemento viene aggiunto all'elenco. Poiché gli elementi componibili MovieOverview hanno chiavi univoche, Compose riconosce le istanze MovieOverview che non sono cambiate e può riutilizzarle; gli effetti collaterali continueranno a essere eseguiti.Alcuni elementi componibili hanno il supporto integrato per l'elemento componibile key. Ad esempio, LazyColumn accetta la specifica di una key personalizzata nel DSL items.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Ignorare se gli input non sono cambiati
Durante la ricomposizione, l'esecuzione di alcune funzioni di elementi componibili idonei può essere ignorata completamente se i relativi input non sono cambiati rispetto alla composizione precedente.
Una funzione componibile è idonea per l'ignoramento a meno che:
- La funzione ha un tipo restituito non
Unit - La funzione è annotata con
@NonRestartableComposableo@NonSkippableComposable - Un parametro obbligatorio è di un tipo non stabile
Esiste una modalità di compilatore sperimentale, Strong Skipping, che rilassa l'ultimo requisito.
Affinché un tipo sia considerato stabile, deve rispettare il seguente contratto:
- Il risultato di
equalsper due istanze sarà sempre lo stesso per le stesse due istanze. - Se una proprietà pubblica del tipo cambia, la composizione riceverà una notifica.
- Anche tutti i tipi di proprietà pubbliche sono stabili.
Esistono alcuni tipi comuni importanti che rientrano in questo contratto che il compilatore Compose tratterà come stabili, anche se non sono contrassegnati esplicitamente come stabili utilizzando l'annotazione @Stable:
- Tutti i tipi di valori primitivi:
Boolean,Int,Long,Float,Chare così via. - Stringhe
- Tutti i tipi di funzione (lambda)
Tutti questi tipi sono in grado di seguire il contratto di stabilità perché sono immutabili. Poiché i tipi immutabili non cambiano mai, non devono mai notificare la modifica alla composizione, quindi è molto più facile seguire questo contratto.
Un tipo degno di nota che è stabile ma è mutabile è il tipo MutableState di Compose. Se un valore è contenuto in un MutableState, l'oggetto di stato complessivo è
considerato stabile, poiché Compose riceverà una notifica di eventuali modifiche alla
.value proprietà di State.
Quando tutti i tipi passati come parametri a un elemento componibile sono stabili, i valori dei parametri vengono confrontati per verificare l'uguaglianza in base alla posizione dell'elemento componibile nell'albero dell'UI. La ricomposizione viene ignorata se tutti i valori sono invariati rispetto alla chiamata precedente.
Compose considera un tipo stabile solo se può dimostrarlo. Ad esempio, un'interfaccia viene generalmente trattata come non stabile e anche i tipi con proprietà pubbliche mutabili la cui implementazione potrebbe essere immutabile non sono stabili.
Se Compose non è in grado di dedurre che un tipo è stabile, ma vuoi forzare
Compose a trattarlo come stabile, contrassegnalo con l'
@Stable annotazione.
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
Nello snippet di codice riportato sopra, poiché UiState è un'interfaccia, Compose potrebbe normalmente considerare questo tipo come non stabile. Aggiungendo l'annotazione @Stable, indichi a Compose che questo tipo è stabile, consentendogli di favorire le ricomposizioni intelligenti. Ciò significa anche che Compose tratterà tutte le sue implementazioni come stabili se l'interfaccia viene utilizzata come tipo di parametro.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Stato e Jetpack Compose
- Effetti collaterali in Compose
- Salvare lo stato dell'UI in Compose