In Compose la UI è immutabile: non è possibile aggiornarla dopo il disegno. Ciò che puoi controllare è lo stato della tua UI. Ogni volta che lo stato dell'UI cambia, Compose ricrea le parti dell'albero dell'UI che sono cambiate. I composable possono accettare lo stato ed esporre gli eventi. Ad esempio, un
TextField accetta un valore ed espone un callback onValueChange che
richiede al gestore di callback di modificare il valore.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Poiché i composable accettano lo stato ed espongono gli eventi, il pattern di flusso di dati unidirezionale si adatta bene a Jetpack Compose. Questa guida si concentra su come implementare il pattern di flusso di dati unidirezionale in Compose, come implementare eventi e state holder e come utilizzare i ViewModel in Compose.
Flusso di dati unidirezionale
Un flusso di dati unidirezionale (UDF) è un pattern di progettazione in cui lo stato scorre verso il basso e gli eventi scorrono verso l'alto. 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.
Il ciclo di aggiornamento della UI per un'app che utilizza il flusso di dati unidirezionale è il seguente:
- Evento: una parte della UI genera un evento e lo passa verso l'alto, ad esempio un clic su un pulsante passato al ViewModel per la gestione; oppure un evento viene passato da altri livelli dell'app, ad esempio per indicare che la sessione utente è scaduta.
- Aggiorna stato: un gestore eventi potrebbe modificare lo stato.
- Stato di visualizzazione: il detentore dello stato passa lo stato e la UI lo visualizza.
Seguire questo pattern quando utilizzi Jetpack Compose offre diversi vantaggi:
- Testabilità: separare lo stato dall'interfaccia utente che lo visualizza semplifica il test di entrambi in isolamento.
- Incapsulamento dello stato: poiché lo stato può essere aggiornato solo in un'unica posizione e poiché esiste un'unica fonte di verità per lo stato di un composable, è meno probabile che si creino bug a causa di stati incoerenti.
- Coerenza della UI: tutti gli aggiornamenti dello stato vengono immediatamente visualizzati nella UI tramite l'utilizzo di holder di stato osservabili, come
StateFlowoLiveData.
Flusso di dati unidirezionale in Jetpack Compose
I composable funzionano in base allo stato e agli eventi. Ad esempio, un TextField viene aggiornato solo quando il relativo parametro value viene aggiornato ed espone un callback onValueChange, ovvero un evento che richiede la modifica del valore in uno nuovo. Compose
definisce l'oggetto State come contenitore di valori e le modifiche al valore dello stato
attivano una ricomposizione. Puoi conservare lo stato in un
remember { mutableStateOf(value) } o in un
rememberSaveable { mutableStateOf(value) a seconda del tempo per cui devi
ricordare il valore.
Il tipo di valore del composable TextField è String, quindi può provenire
da qualsiasi posizione: da un valore hardcoded, da un ViewModel o passato dal
composable padre. Non devi memorizzarlo in un oggetto State, ma devi aggiornare il valore quando viene chiamato onValueChange.
Definisci i parametri componibili
Quando definisci i parametri di stato di un elemento componibile, tieni presente quanto segue:
- Quanto è riutilizzabile o flessibile il componente componibile?
- In che modo i parametri di stato influiscono sul rendimento di questo elemento componibile?
Per favorire il disaccoppiamento e il riutilizzo, ogni elemento componibile deve contenere la quantità minima di informazioni possibile. Ad esempio, quando crei un composable per contenere l'intestazione di un articolo di notizie, preferisci passare solo le informazioni da visualizzare, anziché l'intero articolo di notizie:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
A volte, l'utilizzo di singoli parametri migliora anche il rendimento, ad esempio se
News contiene più informazioni di title e subtitle, ogni volta che
una nuova istanza di News viene passata a Header(news), il composable
viene ricomposto, anche se title e subtitle non sono cambiati.
Valuta attentamente il numero di parametri che trasmetti. Una funzione con troppi parametri ne riduce l'ergonomia, quindi in questo caso è preferibile raggrupparli in una classe.
Eventi in Crea
Ogni input alla tua app deve essere rappresentato come un evento: tocchi, modifiche del testo
e persino timer o altri aggiornamenti. Poiché questi eventi modificano lo stato della UI,
ViewModel deve gestirli e aggiornare lo stato della UI.
Il livello UI non deve mai cambiare stato al di fuori di un gestore di eventi perché ciò può introdurre incoerenze e bug nell'applicazione.
Preferisci passare valori immutabili per le espressioni lambda di stato e del gestore eventi. Questo approccio presenta i seguenti vantaggi:
- Migliori la riusabilità.
- Verifica che la tua UI non modifichi direttamente il valore dello stato.
- Eviti problemi di concorrenza perché ti assicuri che lo stato non venga modificato da un altro thread.
- Spesso, riduci la complessità del codice.
Ad esempio, un composable che accetta un String e una lambda come parametri può
essere chiamato da molti contesti ed è altamente riutilizzabile. Supponiamo che la barra
delle app nella tua app mostri sempre del testo e abbia un
pulsante Indietro. Puoi definire un composable MyAppTopAppBar più generico
che riceve il testo e l'handle del pulsante Indietro come parametri:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModel, stati ed eventi: un esempio
Utilizzando ViewModel e mutableStateOf, puoi anche introdurre un flusso di dati unidirezionale nella tua app se si verifica una delle seguenti condizioni:
- Lo stato della UI viene esposto utilizzando holder di stato osservabili, come
StateFlowoLiveData. ViewModelgestisce gli eventi provenienti dalla UI o da altri livelli dell'app e aggiorna il contenitore di stato in base agli eventi.
Ad esempio, quando implementi una schermata di accesso, toccando un pulsante Accedi l'app dovrebbe mostrare un indicatore di avanzamento e una chiamata di rete. Se l'accesso è riuscito, l'app passa a un'altra schermata; in caso di errore, l'app mostra uno snackbar. Ecco come modellare lo stato dello schermo e l'evento:
Lo schermo ha quattro stati:
- Disconnesso: quando l'utente non ha ancora eseguito l'accesso.
- In corso: quando l'app tenta di far accedere l'utente eseguendo una chiamata di rete.
- Errore: quando si è verificato un errore durante l'accesso.
- Accesso eseguito: quando l'utente ha eseguito l'accesso.
Puoi modellare questi stati come una classe sigillata. ViewModel espone lo stato
come State, imposta lo stato iniziale e lo aggiorna in base alle necessità. ViewModel gestisce anche l'evento di accesso esponendo un metodo onSignIn().
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
Oltre all'API mutableStateOf, Compose fornisce
estensioni per LiveData, Flow e Observable per registrarsi come
listener e rappresentare il valore come stato.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
Scopri di più
Per saperne di più sull'architettura in Jetpack Compose, consulta le seguenti risorse:
Campioni
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- State e Jetpack Compose
- Salvare lo stato dell'interfaccia utente in Compose
- Gestire l'input dell'utente