Lo stato di un'app è qualsiasi valore che può cambiare nel tempo. Si tratta di una definizione molto ampia che comprende qualsiasi cosa, da un database Room a una variabile in una classe.
Tutte le app per Android mostrano lo stato all'utente. Alcuni esempi di stato nelle app per Android:
- Una notifica che viene visualizzata quando non è possibile stabilire una connessione di rete.
- Un post del blog e i commenti associati.
- Animazioni a increspatura sui pulsanti che vengono riprodotte quando un utente fa clic su di essi.
- Adesivi che un utente può disegnare sopra un'immagine.
Jetpack Compose ti aiuta a definire in modo esplicito dove e come archiviare e utilizzare lo stato in un'app per Android. Questa guida si concentra sulla connessione tra lo stato e i composable e sulle API che Jetpack Compose offre per lavorare più facilmente con lo stato.
Stato e composizione
Compose è dichiarativo e, pertanto, l'unico modo per aggiornarlo è chiamare
lo stesso elemento componibile con nuovi argomenti. Questi argomenti sono rappresentazioni dello stato dell'interfaccia utente. Ogni volta che uno stato viene aggiornato, si verifica una ricomposizione. Di conseguenza, elementi come TextField
non vengono aggiornati automaticamente come nelle visualizzazioni basate su XML imperativo. Per aggiornarsi di conseguenza, a un elemento componibile deve essere comunicato esplicitamente il nuovo stato.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
Se esegui questo comando e provi a inserire del testo, vedrai che non succede nulla. Questo perché TextField
non si aggiorna automaticamente, ma quando cambia il parametro value
. Ciò è dovuto al modo in cui funzionano la composizione e la ricomposizione in
Compose.
Per scoprire di più sulla composizione e la ricomposizione iniziali, consulta Pensare in Compose.
Stato nei composable
Le funzioni componibili possono utilizzare l'API
remember
per archiviare un oggetto in memoria. Un valore calcolato da remember
viene memorizzato nella composizione durante la composizione iniziale e il valore memorizzato viene restituito durante la ricomposizione.
remember
può essere utilizzato per archiviare oggetti modificabili e immutabili.
mutableStateOf
crea un oggetto osservabile
MutableState<T>
,
che è un tipo osservabile integrato con il runtime di Compose.
interface MutableState<T> : State<T> {
override var value: T
}
Qualsiasi modifica alle pianificazioni di value
comporta la ricomposizione di qualsiasi funzione componibile
che legge value
.
Esistono tre modi per dichiarare un oggetto MutableState
in un composable:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
Queste dichiarazioni sono equivalenti e vengono fornite come zucchero sintattico per diversi utilizzi dello stato. Devi scegliere quello che produce il codice più facile da leggere nel composable che stai scrivendo.
La sintassi del delegato by
richiede i seguenti import:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Puoi utilizzare il valore memorizzato come parametro per altri componenti componibili o anche come
logica nelle istruzioni per modificare i componenti componibili visualizzati. Ad esempio, se
non vuoi visualizzare il saluto se il nome è vuoto, utilizza lo stato in un'istruzione
if
:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
Sebbene remember
ti aiuti a mantenere lo stato durante le ricomposizioni, lo stato non viene mantenuto durante le modifiche alla configurazione. Per farlo, devi utilizzare
rememberSaveable
. rememberSaveable
salva automaticamente qualsiasi valore che può essere
salvato in un Bundle
. Per gli altri valori, puoi passare un oggetto di salvataggio personalizzato.
Altri tipi di stato supportati
Compose non richiede l'utilizzo di MutableState<T>
per mantenere lo stato, ma supporta altri tipi osservabili. Prima di leggere un altro tipo di osservabile in
Compose, devi convertirlo in un State<T>
in modo che i composable possano
ricomporsi automaticamente quando lo stato cambia.
Compose è dotato di funzioni per creare State<T>
dai tipi di observable comuni utilizzati nelle app per Android. Prima di utilizzare queste integrazioni, aggiungi gli
artefatti appropriati, come descritto di seguito:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
raccoglie i valori da unFlow
in modo consapevole del ciclo di vita, consentendo alla tua app di conservare le risorse dell'app. Rappresenta l'ultimo valore emesso da ComposeState
. Utilizza questa API come metodo consigliato per raccogliere i flussi nelle app per Android.La seguente dipendenza è richiesta nel file
build.gradle
(deve essere 2.6.0-beta01 o versioni successive):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
-
collectAsState
è simile acollectAsStateWithLifecycle
, perché raccoglie anche i valori da unFlow
e li trasforma inState
di Compose.Utilizza
collectAsState
per il codice indipendente dalla piattaforma anzichécollectAsStateWithLifecycle
, che è solo per Android.Non sono richieste dipendenze aggiuntive per
collectAsState
, perché è disponibile incompose-runtime
. -
observeAsState()
inizia a osservare questoLiveData
e ne rappresenta i valori tramiteState
.La seguente dipendenza è obbligatoria nel file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.8.1"
}
-
subscribeAsState()
sono funzioni di estensione che trasformano gli stream reattivi di RxJava2 (ad es.Single
,Observable
,Completable
) inState
di Compose.La seguente dipendenza è obbligatoria nel file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.8.1"
}
-
subscribeAsState()
sono funzioni di estensione che trasformano gli stream reattivi di RxJava3 (ad es.Single
,Observable
,Completable
) inState
di Compose.La seguente dipendenza è obbligatoria nel file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.8.1"
}
Stateful e stateless
Un composable che utilizza remember
per archiviare un oggetto crea uno stato interno, rendendo il composable stateful. HelloContent
è un esempio di composizione
con stato perché contiene e modifica internamente il suo stato name
. Può
essere utile in situazioni in cui un chiamante non ha bisogno di controllare lo stato e può
utilizzarlo senza doverlo gestire personalmente. Tuttavia, i composable con
stato interno tendono a essere meno riutilizzabili e più difficili da testare.
Un composable senza stato è un composable che non contiene alcuno stato. Un modo semplice per ottenere l'assenza di stato è utilizzare l'innalzamento dello stato.
Quando sviluppi composable riutilizzabili, spesso vuoi esporre sia una versione con stato sia una versione senza stato dello stesso composable. La versione con stato è comoda per i chiamanti che non si preoccupano dello stato, mentre la versione senza stato è necessaria per i chiamanti che devono controllare o sollevare lo stato.
Innalzamento dello stato
Il sollevamento dello stato in Compose è un pattern di spostamento dello stato al chiamante di un composable per rendere un composable stateless. Il pattern generale per l'innalzamento dello stato in Jetpack Compose consiste nel sostituire la variabile di stato con due parametri:
value: T
: il valore corrente da visualizzareonValueChange: (T) -> Unit
:un evento che richiede la modifica del valore, doveT
è il nuovo valore proposto
Tuttavia, non sei limitato a onValueChange
. Se per il componente sono appropriati eventi più specifici, devi definirli utilizzando le espressioni lambda.
Lo stato sottoposto a hoisting in questo modo presenta alcune proprietà importanti:
- Unica fonte attendibile: spostando lo stato anziché duplicarlo, ci assicuriamo che esista un'unica fonte attendibile. In questo modo si evitano bug.
- Incapsulati:solo i composable stateful possono modificare il proprio stato. È completamente interno.
- Condivisibile:lo stato sollevato può essere condiviso con più composable. Se
volessi leggere
name
in un altro elemento componibile, l'hoisting ti consentirebbe di farlo. - Intercettabile:i chiamanti dei composable stateless possono decidere di ignorare o modificare gli eventi prima di cambiare lo stato.
- Disaccoppiato:lo stato dei composable stateless può essere archiviato
ovunque. Ad esempio, ora è possibile spostare
name
in unViewModel
.
Nell'esempio, estrai name
e onValueChange
da
HelloContent
e li sposti verso l'alto nella struttura ad albero in un elemento componibile HelloScreen
che
chiama HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
Sollevando lo stato da HelloContent
, è più facile ragionare sul
composabile, riutilizzarlo in diverse situazioni e testarlo. HelloContent
è
disaccoppiato dal modo in cui viene memorizzato il suo stato. Il disaccoppiamento significa che se modifichi o
sostituisci HelloScreen
, non devi modificare l'implementazione di HelloContent
.

Il pattern in cui lo stato diminuisce e gli eventi aumentano è chiamato
flusso di dati unidirezionale. In questo caso, lo stato scende da HelloScreen
a HelloContent
e gli eventi aumentano da HelloContent
a HelloScreen
. Seguendo il flusso di dati unidirezionale, puoi separare i composable che mostrano lo stato nell'UI dalle parti dell'app che archiviano e modificano lo stato.
Per scoprire di più, consulta la pagina Dove sollevare lo stato.
Ripristino dello stato in Compose
L'API rememberSaveable
si comporta in modo simile a remember
perché
mantiene lo stato tra le ricomposizioni e anche tra le attività o la ricreazione dei processi
utilizzando il meccanismo di stato dell'istanza salvata. Ad esempio, questo accade
quando lo schermo viene ruotato.
Modalità di archiviazione dello stato
Tutti i tipi di dati aggiunti a Bundle
vengono salvati automaticamente. Se vuoi salvare qualcosa che non può essere aggiunto a Bundle
, hai diverse opzioni.
Parcelize
La soluzione più semplice è aggiungere l'annotazione
@Parcelize
all'oggetto. L'oggetto diventa trasferibile e può essere raggruppato. Ad esempio, questo codice crea un tipo di dati City
parcelable e lo salva nello stato.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
Se per qualche motivo @Parcelize
non è adatto, puoi utilizzare mapSaver
per
definire la tua regola per convertire un oggetto in un insieme di valori che il
sistema può salvare in Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
Per evitare di dover definire le chiavi per la mappa, puoi anche utilizzare listSaver
e utilizzare i relativi indici come chiavi:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
Contenitori di stato in Compose
Il semplice sollevamento dello stato può essere gestito nelle funzioni componibili stesse. Tuttavia, se la quantità di stato da monitorare aumenta o se si presenta la logica da eseguire nelle funzioni componibili, è buona norma delegare la logica e le responsabilità dello stato ad altre classi: i titolari dello stato.
Per scoprire di più, consulta la documentazione relativa all'innalzamento dello stato in Compose o, più in generale, la pagina State holder e stato dell'UI nella guida all'architettura.
Ricalcolare i valori memorizzati quando cambiano le chiavi
L'API remember
viene spesso utilizzata insieme a MutableState
:
var name by remember { mutableStateOf("") }
In questo caso, l'utilizzo della funzione remember
fa sì che il valore MutableState
sopravviva
alle ricomposizioni.
In generale, remember
accetta un parametro lambda calculation
. Quando remember
viene eseguito per la prima volta, richiama la funzione lambda calculation
e memorizza il risultato. Durante
la ricomposizione, remember
restituisce l'ultimo valore memorizzato.
Oltre allo stato della memorizzazione nella cache, puoi utilizzare remember
per archiviare qualsiasi oggetto o
risultato di un'operazione nella composizione che è costoso inizializzare o
calcolare. Potresti non voler ripetere questo calcolo in ogni ricomposizione.
Un esempio è la creazione di questo oggetto ShaderBrush
, che è un'operazione
costosa:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember
memorizza il valore finché non esce dalla composizione. Tuttavia, esiste un modo per invalidare il valore memorizzato nella cache. L'API remember
accetta anche un parametro key
o
keys
. Se una di queste chiavi cambia, la volta successiva che la funzione
ricompone, remember
invalida la cache ed esegue di nuovo il blocco lambda di calcolo. Questo meccanismo ti consente di controllare la durata di un
oggetto nella composizione. Il calcolo rimane valido finché gli input
cambiano, anziché finché il valore memorizzato non esce dalla composizione.
I seguenti esempi mostrano come funziona questo meccanismo.
In questo snippet viene creato e utilizzato un ShaderBrush
come sfondo
di un composable Box
. remember
memorizza l'istanza ShaderBrush
perché è costoso ricrearla, come spiegato in precedenza. remember
accetta
avatarRes
come parametro key1
, ovvero l'immagine di sfondo selezionata. Se avatarRes
cambia, il pennello viene ricomposto con la nuova immagine e riapplicato a Box
. Ciò può verificarsi quando l'utente seleziona un'altra immagine da utilizzare come
sfondo da un selettore.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
Nel seguente snippet, lo stato viene sollevato a una classe di contenitore di stato semplice
MyAppState
. Espone una funzione rememberMyAppState
per inizializzare un'istanza della classe utilizzando remember
. L'esposizione di queste funzioni per creare un'istanza che sopravvive alle ricomposizioni è un pattern comune in Compose. La funzione
rememberMyAppState
riceve windowSizeClass
, che funge da
parametro key
per remember
. Se questo parametro cambia, l'app deve
ricreare la classe di contenitore dello stato semplice con il valore più recente. Ciò può verificarsi se, ad esempio, l'utente ruota il dispositivo.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose utilizza l'implementazione equals della classe per decidere se una chiave è cambiata e invalidare il valore memorizzato.
Archiviare lo stato con chiavi oltre la ricomposizione
L'API rememberSaveable
è un wrapper intorno a remember
che può archiviare
i dati in un Bundle
. Questa API consente allo stato di sopravvivere non solo
alla ricomposizione, ma anche alla ricreazione dell'attività e all'interruzione del processo avviata dal sistema.
rememberSaveable
riceve i parametri input
per lo stesso scopo per cui
remember
riceve keys
. La cache viene invalidata quando uno qualsiasi degli input
cambia. La volta successiva che la funzione viene ricomposta, rememberSaveable
viene eseguito nuovamente
il blocco lambda di calcolo.
Nell'esempio seguente, rememberSaveable
memorizza userTypedQuery
finché
typedQuery
non cambia:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Scopri di più
Per saperne di più su State e Jetpack Compose, consulta le seguenti risorse aggiuntive.
Campioni
Codelab
Video
Blog
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Architettura dell'interfaccia utente di Compose
- Salvare lo stato dell'interfaccia utente in Compose
- Effetti collaterali in Compose