CompositionLocal
è uno strumento per
la trasmissione implicita dei dati
tramite la composizione. In questa pagina,
scopri cos'è un CompositionLocal
in modo più dettagliato e come crearne uno
CompositionLocal
e capire se CompositionLocal
è una soluzione adeguata
il tuo caso d'uso.
Ti presentiamo CompositionLocal
Di solito in Compose, i dati scorrono verso il basso attraverso Struttura dell'interfaccia utente come parametri per ogni funzione componibile. In questo modo, le dipendenze di un composable diventano esplicite. Tuttavia, questo può essere complicato per i dati spesso e ampiamente utilizzate, come colori o stili di caratteri. Vedi l'esempio riportato di seguito:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
Per non dover passare i colori come dipendenza esplicita del parametro a
più componibili, Compose offre CompositionLocal
, che consente
per creare oggetti con nome con ambito ad albero che possono essere usati come modo implicito per avere
flusso di dati attraverso l'albero della UI.
In genere agli elementi CompositionLocal
viene fornito un valore in un determinato nodo
dell'albero della UI. Questo valore può essere utilizzato dai suoi elementi composibili discendenti senza dover dichiarare CompositionLocal
come parametro nella funzione componibile.
CompositionLocal
è ciò che viene utilizzato dal tema Material.
MaterialTheme
è
che fornisce tre istanze CompositionLocal
: colorScheme
,
typography
e shapes
, che ti consentono di recuperarli in un secondo momento in qualsiasi discendente
parte della composizione.
Nello specifico, si tratta delle proprietà LocalColorScheme
, LocalShapes
e
LocalTypography
a cui puoi accedere tramite gli attributi MaterialTheme
colorScheme
, shapes
e typography
.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colorScheme, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colorScheme.primary ) }
Un'istanza di CompositionLocal
è limitata a una parte della composizione, in modo da poter fornire valori diversi a diversi livelli dell'albero. Il valore current
di CompositionLocal
corrisponde al valore più vicino fornito da un
predecessore in quella parte della composizione.
Per fornire un nuovo valore a un CompositionLocal
, utilizza il metodo
CompositionLocalProvider
e la relativa provides
funzione infissa che associa una chiave CompositionLocal
a una value
. La
content
lambda di CompositionLocalProvider
riceverà i valori forniti
quando accedi alla proprietà current
di CompositionLocal
. Quando
viene fornito un nuovo valore, Compose ricompone parti della Composizione che
CompositionLocal
.
Ad esempio, LocalContentColor
CompositionLocal
contiene il colore preferito dei contenuti utilizzato per il testo e
l'iconografia per garantire che sia in contrasto con il colore di sfondo corrente. Nell'esempio seguente, CompositionLocalProvider
viene utilizzato per fornire valori diversi per parti diverse della composizione.
@Composable fun CompositionLocalExample() { MaterialTheme { // Surface provides contentColorFor(MaterialTheme.colorScheme.surface) by default // This is to automatically make text and other content contrast to the background // correctly. Surface { Column { Text("Uses Surface's provided content color") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.primary) { Text("Primary color provided by LocalContentColor") Text("This Text also uses primary as textColor") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { DescendantExample() } } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the error color now") }
Figura 1. Anteprima del componibile CompositionLocalExample
.
Nell'ultimo esempio, le istanze CompositionLocal
sono state utilizzate internamente
dai composabili Material. Per accedere al valore corrente di un CompositionLocal
,
utilizza la sua current
proprietà. Nell'esempio seguente, il valore Context
corrente di LocalContext
CompositionLocal
, comunemente utilizzato nelle app per Android, viene utilizzato per formattare
il testo:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
Creazione del tuo CompositionLocal
in corso...
CompositionLocal
è uno strumento per trasmettere i dati in modo implicito tramite la composizione.
Un altro indicatore chiave per l'utilizzo di CompositionLocal
è quando il parametro è trasversale e i livelli intermedi di implementazione non devono essere a conoscenza della sua esistenza, perché la consapevolezza di questi livelli intermedi limiterebbe l'utilità del composable. Ad esempio, la query per le autorizzazioni Android
offerti da CompositionLocal
. Un selettore media componibile
aggiungere nuove funzionalità per accedere ai contenuti protetti da autorizzazione nella
dispositivo senza modificare l'API e richiedere ai chiamanti del selettore media di
tieni presente questo contesto aggiuntivo
utilizzato dall'ambiente.
Tuttavia, CompositionLocal
non è sempre la soluzione migliore. sconsigliamo di abusare di CompositionLocal
perché presenta alcuni svantaggi:
CompositionLocal
rende più difficile il ragionamento sul comportamento di un composable. Come
creano dipendenze implicite, i chiamanti dei componenti componibili che le usano
per assicurarti che sia soddisfatto un valore per ogni CompositionLocal
.
Inoltre, potrebbe non esserci una chiara fonte di dati per questa dipendenza, poiché
possono mutare in qualsiasi parte della composizione. Pertanto, il debug dell'app quando
quando si verifica un problema può essere più difficile in quanto è necessario
Composizione per vedere dove è stato fornito il valore current
. Strumenti come Trova utilizzi nell'IDE o l'ispettore del layout di Compose forniscono informazioni sufficienti per attenuare questo problema.
Decidere se utilizzare CompositionLocal
Esistono determinate condizioni che possono rendere CompositionLocal
una buona soluzione
per il tuo caso d'uso:
Un CompositionLocal
deve avere un buon valore predefinito. Se non sono presenti valori predefiniti
devi garantire che sia estremamente difficile per uno sviluppatore
si verifica una situazione in cui non viene fornito un valore per CompositionLocal
.
La mancata indicazione di un valore predefinito può causare problemi e frustrazione durante la creazione di test o l'anteprima di un composable che lo utilizza. CompositionLocal
richiederà sempre di essere fornito esplicitamente.
Evita CompositionLocal
per i concetti che non sono considerati a livello di albero o
a livello di gerarchia secondaria. Un CompositionLocal
ha senso quando può essere
potenzialmente utilizzato da qualsiasi discendente, non da alcuni di essi.
Se il tuo caso d'uso non soddisfa questi requisiti, consulta la sezione Alternative da prendere in considerazione prima di creare un
CompositionLocal
.
Un esempio di cattiva prassi è creare un CompositionLocal
che contenga i
ViewModel
di una determinata schermata in modo che tutti i componibili al suo interno possano
ottenere un riferimento a ViewModel
per eseguire una logica. Questa è una cattiva prassi
perché non tutti i composabili sotto una determinata struttura ad albero dell'interfaccia utente devono conoscere un
ViewModel
. La best practice consiste nel passare ai composabili solo le informazioni di cui hanno bisogno seguendo lo schema in cui lo stato scorre verso il basso e gli eventi verso l'alto. Questo approccio renderà i tuoi composabili più riutilizzabili e più facili da testare.
Creazione di un CompositionLocal
Per creare un CompositionLocal
sono necessarie due API:
compositionLocalOf
: la modifica del valore fornito durante la ricompozione invalida soltanto i contenuti che leggono il suocurrent
valore.staticCompositionLocalOf
: A differenza dicompositionLocalOf
, le letture di unstaticCompositionLocalOf
non sono tracciata da Compose. La modifica del valore causa la totalità delcontent
lambda in cui viene fornito per ricomporreCompositionLocal
, invece di ma solo nei punti in cui viene letto il valorecurrent
nella composizione.
Se è altamente improbabile che il valore fornito a CompositionLocal
cambi o
non cambierà mai, utilizza staticCompositionLocalOf
per ottenere vantaggi in termini di rendimento.
Ad esempio, il sistema di progettazione di un'app potrebbe essere opinabile per il modo in cui i composabili vengono elevati utilizzando un'ombra per il componente dell'interfaccia utente. Poiché i diversi
l'elevazione per l'app deve propagarsi in tutto l'albero dell'interfaccia utente, utilizziamo
CompositionLocal
. Poiché il valore CompositionLocal
viene derivato in modo condizionale
in base al tema del sistema, utilizziamo l'API compositionLocalOf
:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
Fornire valori a CompositionLocal
La CompositionLocalProvider
l'elemento componibile associa i valori a CompositionLocal
istanze per il
gerarchia. Per fornire un nuovo valore a un CompositionLocal
, utilizza la funzione infix provides
che associa una chiave CompositionLocal
a un value
come segue:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
Consumare CompositionLocal
CompositionLocal.current
restituisce il valore fornito dal CompositionLocalProvider
più vicino che fornisce un valore a CompositionLocal
:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition MyCard(elevation = LocalElevations.current.card) { // Content } }
Alternative da prendere in considerazione
Un valore CompositionLocal
potrebbe essere una soluzione eccessiva per alcuni casi d'uso. Se le tue
non soddisfa i criteri specificati nella sezione Decidere se utilizzare
ComposizioneLocal, un'altra soluzione potrebbe essere probabilmente migliore
più adatti al tuo caso d'uso.
Passare parametri espliciti
È buona norma dichiarare esplicitamente le dipendenze del composable. Ti consigliamo di passare ai composabili solo ciò di cui hanno bisogno. Per incoraggiare il disaccoppiamento e il riutilizzo dei composabili, ogni componibile deve contenere la minima quantità di informazioni possibile.
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
Inversione di controllo
Un altro modo per evitare di trasferire dipendenze non necessarie a un componibile è tramite inversione dei controlli. Anziché il componente derivato, è il componente principale a eseguire la logica in base alla dipendenza.
Guarda l'esempio seguente in cui un discendente deve attivare la richiesta per caricare alcuni dati:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
A seconda dei casi, MyDescendant
potrebbe avere molte responsabilità. Inoltre,
la trasmissione di MyViewModel
come dipendenza rende MyDescendant
meno riutilizzabile poiché
ora sono accoppiati. Prendi in considerazione l'alternativa che non passa la dipendenza al discendente e utilizza i principi di inversione del controllo che rendono l'antenato responsabile dell'esecuzione della logica:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
Questo approccio può essere più adatto ad alcuni casi d'uso in quanto disaccoppia figlio dei suoi predecessori immediati. I composabili di primo livello tendono a diventare più complessi a favore di composabili di livello inferiore più flessibili.
Analogamente, i lambda dei contenuti @Composable
possono essere utilizzati nello stesso modo per ottenere gli stessi vantaggi:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Struttura di un tema in Scrivi
- Utilizzare le visualizzazioni in Compose
- Kotlin per Jetpack Compose