In Compose, l'UI è immutabile: non è possibile aggiornarla dopo che è stata disegnata. Quello che puoi controllare è lo stato dell'UI. Ogni volta che lo stato dell'
UI cambia, Compose ricrea le parti dell'albero dell'UI che sono state
modificate. 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 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 gli eventi e i titolari di stato e come lavorare con 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 disaccoppiare i composable che mostrano lo stato nell'UI dalle parti dell'app che memorizzano e modificano lo stato.
Il ciclo di aggiornamento dell'UI per un'app che utilizza il flusso di dati unidirezionale è il seguente:
- Evento: una parte dell'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 di eventi potrebbe modificare lo stato.
- Mostra stato: il contenitore di stato passa lo stato e l'UI lo mostra.
L'utilizzo di questo pattern con Jetpack Compose offre diversi vantaggi:
- Testabilità: il disaccoppiamento dello stato dall'UI che lo mostra semplifica il test di entrambi in isolamento.
- Incapsulamento dello stato: poiché lo stato può essere aggiornato solo in un unico punto e esiste una sola fonte di verità per lo stato di un composable, è meno probabile che si creino bug a causa di stati incoerenti.
- Coerenza dell'UI: tutti gli aggiornamenti dello stato vengono immediatamente riflessi nell'UI tramite
l'utilizzo di titolari 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 un titolare di valori e le modifiche al valore dello stato attivano una ricomposizione. Puoi mantenere lo stato in un remember { mutableStateOf(value) } o in un rememberSaveable { mutableStateOf(value) a seconda di per quanto tempo devi ricordare il valore.
Il tipo di valore del composable TextField è String, quindi può provenire da qualsiasi luogo: da un valore hardcoded, da un ViewModel o passato dal composable padre. Non devi mantenerlo in un oggetto State, ma devi aggiornare il valore quando viene chiamato onValueChange.
Definisci i parametri composable
Quando definisci i parametri di stato di un composable, tieni presente le seguenti domande:
- Quanto è riutilizzabile o flessibile il composable?
- In che modo i parametri di stato influiscono sulle prestazioni di questo composable?
Per promuovere il disaccoppiamento e il riutilizzo, ogni composable deve contenere la minor quantità possibile di informazioni. 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 le prestazioni. Ad esempio, se
News contiene più informazioni di title e subtitle, ogni volta che viene passata una
nuova istanza di News in Header(news), il composable verrà
ricomposto, anche se title e subtitle non sono stati modificati.
Valuta attentamente il numero di parametri che passi. Una funzione con troppi parametri riduce l'ergonomia della funzione, quindi in questo caso è preferibile raggrupparli in una classe.
Eventi in Compose
Ogni input dell'app deve essere rappresentato come un evento: tocchi, modifiche del testo e persino timer o altri aggiornamenti. Poiché questi eventi modificano lo stato dell'UI, il ViewModel deve gestirli e aggiornare lo stato dell'UI.
Il livello UI non deve mai modificare lo stato al di fuori di un gestore di eventi, perché ciò può introdurre incoerenze e bug nell'applicazione.
Preferisci passare valori immutabili per le lambda di stato e gestore di eventi. Questo approccio offre i seguenti vantaggi:
- Migliori la riusabilità.
- Verifichi che l'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 una String e una lambda come parametri può essere chiamato da molti contesti ed è altamente riutilizzabile. Supponiamo che la barra delle app in alto nella tua app mostri sempre il testo e abbia un pulsante Indietro. Puoi definire un composable MyAppTopAppBar più generico che riceve il testo e il gestore 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 il flusso di dati unidirezionale nella tua app se è vera una delle seguenti condizioni:
- Lo stato dell'UI viene esposto utilizzando titolari di stato osservabili, come
StateFlowoLiveData. - Il
ViewModelgestisce gli eventi provenienti dall'UI o da altri livelli dell'app e aggiorna il titolare di stato in base agli eventi.
Ad esempio, quando implementi una schermata di accesso, toccare un pulsante Accedi dovrebbe far sì che l'app mostri una rotellina di avanzamento e una chiamata di rete. Se l'accesso è andato a buon fine, l'app passa a un'altra schermata; in caso di errore, l'app mostra uno snackbar. Ecco come modellare lo stato della schermata e l'evento:
La schermata ha quattro stati:
- Signed out: quando l'utente non ha ancora eseguito l'accesso.
- In progress: quando l'app sta tentando di accedere all'utente eseguendo una chiamata di rete.
- Errore: quando si è verificato un errore durante l'accesso.
- Signed in: quando l'utente ha eseguito l'accesso.
Puoi modellare questi stati come una classe sealed. Il ViewModel espone lo stato come State, imposta lo stato iniziale e aggiorna lo stato in base alle esigenze. Il 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 scoprire di più sull'architettura in Jetpack Compose, consulta le seguenti risorse:
Esempi
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Stato e Jetpack Compose
- Salva lo stato dell'UI in Compose
- Gestisci l'input dell'utente