CompositionLocal ist ein Tool zum impliziten Übergeben von Daten durch die Komposition. Auf dieser Seite erfahren Sie mehr über CompositionLocal, wie Sie Ihre eigene CompositionLocal erstellen und ob CompositionLocal eine gute Lösung für Ihren Anwendungsfall ist.
Einführung in CompositionLocal
In Compose fließen Daten in der Regel als Parameter für jede zusammensetzbare Funktion durch die UI-Struktur. Dadurch werden die Abhängigkeiten einer zusammensetzbaren Funktion explizit. Dies kann jedoch bei Daten umständlich sein, die sehr häufig und weit verbreitet sind, z. B. Farben oder Schriftstile. Sehen Sie sich das folgende Beispiel an:
@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 ) }
Damit die Farben nicht als explizite Parameterabhängigkeit an
die meisten zusammensetzbaren Funktionen übergeben werden müssen, bietet Compose CompositionLocal. Damit können Sie
benannte Objekte mit Gültigkeitsbereich auf Strukturebene erstellen, die als implizite Möglichkeit verwendet werden können, Daten durch die UI-Struktur zu leiten.
CompositionLocal -Elemente werden in der Regel mit einem Wert in einem bestimmten Knoten der UI-Struktur bereitgestellt. Dieser Wert kann von den zusammensetzbaren Nachfolgern verwendet werden, ohne dass CompositionLocal als Parameter in der zusammensetzbaren Funktion deklariert werden muss.
CompositionLocal wird im Hintergrund vom Material-Design verwendet.
MaterialTheme ist ein Objekt, das drei CompositionLocal-Instanzen bereitstellt: colorScheme, typography und shapes. So können Sie sie später in jedem untergeordneten Teil der Komposition abrufen.
Konkret sind das die Eigenschaften LocalColorScheme, LocalShapes und LocalTypography, auf die Sie über die Attribute colorScheme, shapes und typography von MaterialTheme zugreifen können.
@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 ) }
Eine CompositionLocal-Instanz ist auf einen Teil der Komposition beschränkt , sodass Sie auf verschiedenen Ebenen der Struktur unterschiedliche Werte angeben können. Der current Wert
eines CompositionLocal entspricht dem nächstgelegenen Wert, der von einem
Vorgänger in diesem Teil der Komposition bereitgestellt wird.
Verwenden Sie
CompositionLocalProvider
und die Infix-Funktion provides, um einen neuen Wert für CompositionLocal bereitzustellen. Dabei wird ein CompositionLocal-Schlüssel einem value zugeordnet. Das
content Lambda von CompositionLocalProvider erhält den bereitgestellten
Wert, wenn auf die current Eigenschaft von CompositionLocal zugegriffen wird. Wenn ein neuer Wert bereitgestellt wird, werden Teile der Komposition, die CompositionLocal lesen, neu zusammengesetzt.
Ein Beispiel dafür ist LocalContentColor CompositionLocal. Es enthält die bevorzugte Inhaltsfarbe für Text und Ikonografie, damit sie sich von der aktuellen Hintergrundfarbe abhebt. Im folgenden Beispiel wird CompositionLocalProvider verwendet, um für verschiedene Teile der Komposition unterschiedliche Werte bereitzustellen.
@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") }
CompositionLocalExample.Im letzten Beispiel wurden die CompositionLocal-Instanzen intern von zusammensetzbaren Material-Funktionen verwendet. Verwenden Sie die current
Eigenschaft, um auf den aktuellen Wert von CompositionLocal,
zuzugreifen. Im folgenden Beispiel wird der aktuelle Context-Wert von
LocalContext CompositionLocal, der gängig in Android-Apps verwendet wird, zum Formatieren des Texts verwendet:
@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) }
Eigene CompositionLocal erstellen
CompositionLocal ist ein Tool zum impliziten Übergeben von Daten durch die Komposition.
Ein weiteres wichtiges Signal für die Verwendung von CompositionLocal ist, wenn der Parameter
übergreifend ist und Zwischenebenen der Implementierung nicht darüber informiert werden sollten. Andernfalls würde die Nützlichkeit der zusammensetzbaren Funktion eingeschränkt. Beispielsweise wird die Abfrage nach Android-Berechtigungen im
Hintergrund durch ein CompositionLocal ermöglicht. Eine zusammensetzbare Funktion für die Media Picker
kann neue Funktionen hinzufügen, um auf berechtigungsgeschützte Inhalte auf dem
Gerät zuzugreifen, ohne die API zu ändern. Außerdem müssen Aufrufer der Media Picker nicht
über diesen zusätzlichen Kontext informiert werden, der aus der Umgebung verwendet wird.
CompositionLocal ist jedoch nicht immer die beste Lösung. Wir raten davon ab, CompositionLocal zu häufig zu verwenden , da dies einige Nachteile mit sich bringt:
CompositionLocal erschwert die Nachvollziehbarkeit des Verhaltens einer zusammensetzbaren Funktion. Da sie implizite Abhängigkeiten erstellen, müssen Aufrufer von zusammensetzbaren Funktionen, die sie verwenden, dafür sorgen, dass ein Wert für jede CompositionLocal vorhanden ist.
Außerdem gibt es möglicherweise keine eindeutige Quelle der Wahrheit für diese Abhängigkeit, da sie sich in jedem Teil der Komposition ändern kann. Daher kann das Debuggen der App bei einem Problem schwieriger sein , da Sie in der Komposition nach oben navigieren müssen, um zu sehen, wo der current-Wert bereitgestellt wurde. Tools wie Find
usages in der IDE oder der Compose-Layoutinspektor bieten genügend
Informationen, um dieses Problem zu beheben.
Entscheiden, ob CompositionLocal verwendet werden soll
Unter bestimmten Bedingungen kann CompositionLocal eine gute Lösung für Ihren Anwendungsfall sein:
Eine CompositionLocal sollte einen guten Standardwert haben. Wenn kein Standardwert vorhanden ist, müssen Sie dafür sorgen, dass es für einen Entwickler äußerst schwierig ist, in eine Situation zu geraten, in der kein Wert für CompositionLocal bereitgestellt wird.
Wenn kein Standardwert angegeben wird, kann dies zu Problemen und Frustration führen, da beim Erstellen von Tests oder beim Vorschauen einer zusammensetzbaren Funktion, die CompositionLocal verwendet, immer ein Wert explizit angegeben werden muss.
Vermeiden Sie CompositionLocal für Konzepte, die nicht als auf Strukturebene oder
Unterhierarchieebene betrachtet werden. CompositionLocal ist sinnvoll, wenn es potenziell von jedem Nachfolger verwendet werden kann, nicht nur von einigen.
Wenn Ihr Anwendungsfall diese Anforderungen nicht erfüllt, lesen Sie den
Abschnitt Alternativen berücksichtigen, bevor Sie
CompositionLocal erstellen.
Ein Beispiel für eine schlechte Vorgehensweise ist das Erstellen von CompositionLocal, das das ViewModel eines bestimmten Bildschirms enthält, sodass alle zusammensetzbaren Funktionen auf diesem Bildschirm einen Verweis auf das ViewModel erhalten können, um eine bestimmte Logik auszuführen. Das ist eine schlechte Vorgehensweise, da nicht alle zusammensetzbaren Funktionen unterhalb einer bestimmten UI-Struktur ein ViewModel kennen müssen. Die gute Vorgehensweise besteht darin, nur die Informationen
an zusammensetzbare Funktionen zu übergeben, die sie benötigen, und dabei dem Muster zu folgen, dass der Status nach unten und Ereignisse nach oben fließen. Mit diesem Ansatz werden Ihre zusammensetzbaren Funktionen wiederverwendbarer und einfacher zu testen.
CompositionLocal erstellen
Es gibt zwei APIs zum Erstellen von CompositionLocal:
compositionLocalOf:Wenn der während der Neuzusammensetzung bereitgestellte Wert geändert wird, wird nurder Inhalt ungültig gemacht, der dencurrent-Wert liest.staticCompositionLocalOf: Im Gegensatz zucompositionLocalOfwerden Lesevorgänge vonstaticCompositionLocalOfnicht von Compose erfasst. Wenn der Wert geändert wird, wird das gesamtecontent-Lambda, in demCompositionLocalbereitgestellt wird, neu zusammengesetzt, nicht nur die Stellen, an denen dercurrent-Wert in der Komposition gelesen wird.
Wenn sich der für die CompositionLocal bereitgestellte Wert höchstwahrscheinlich nicht oder
nie ändert, verwenden Sie staticCompositionLocalOf, um die Leistung zu verbessern.
Beispielsweise kann das Designsystem einer App vorschreiben, wie zusammensetzbare Funktionen mithilfe eines Schattens für die UI-Komponente hervorgehoben werden. Da die verschiedenen Erhebungen für die App in der gesamten UI-Struktur weitergegeben werden sollen, verwenden wir CompositionLocal. Da der CompositionLocal-Wert bedingt basierend auf dem Systemdesign abgeleitet wird, verwenden wir die compositionLocalOf-API:
// 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() }
Werte für CompositionLocal bereitstellen
Die zusammensetzbare Funktion CompositionLocalProvider
bindet Werte an CompositionLocal Instanzen für die angegebene
Hierarchie. Verwenden Sie die
provides
Infix-Funktion, um einen neuen Wert für CompositionLocal bereitzustellen. Dabei wird ein CompositionLocal-Schlüssel einem value zugeordnet:
// 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 } } } }
CompositionLocal verwenden
CompositionLocal.current gibt den Wert zurück, der vom nächstgelegenen
CompositionLocalProvider bereitgestellt wird, der einen Wert für CompositionLocal bereitstellt:
@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 } }
Alternativen berücksichtigen
CompositionLocal ist für einige Anwendungsfälle möglicherweise eine übermäßige Lösung. Wenn Ihr
Anwendungsfall die im Abschnitt Entscheidung für oder gegen
CompositionLocal angegebenen Kriterien nicht erfüllt, ist eine andere Lösung wahrscheinlich besser
geeignet.
Explizite Parameter übergeben
Es ist eine gute Gewohnheit, die Abhängigkeiten einer zusammensetzbaren Funktion explizit anzugeben. Wir empfehlen, zusammensetzbaren Funktionen nur das zu übergeben, was sie benötigen. Um die Entkopplung und Wiederverwendung von zusammensetzbaren Funktionen zu fördern, sollte jede zusammensetzbare Funktion so wenige Informationen wie möglich enthalten.
@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 }
Inversion of Control
Eine weitere Möglichkeit, unnötige Abhängigkeiten an eine zusammensetzbare Funktion zu übergeben, ist die Verwendung der Inversion of Control. Anstatt dass der Nachfolger eine Abhängigkeit aufnimmt, um eine bestimmte Logik auszuführen, übernimmt das der Vorgänger.
Sehen Sie sich das folgende Beispiel an, in dem ein Nachfolger die Anfrage zum Laden von Daten auslösen muss:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
Je nach Fall kann MyDescendant viele Aufgaben haben. Außerdem wird MyDescendant durch die Übergabe von MyViewModel als Abhängigkeit weniger wiederverwendbar, da sie jetzt miteinander gekoppelt sind. Sehen Sie sich die Alternative an, bei der die Abhängigkeit nicht an das Nachfolgerelement übergeben wird und die Prinzipien der Inversion of Control verwendet werden, sodass der Vorgänger für die Ausführung der Logik verantwortlich ist:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
Dieser Ansatz kann für einige Anwendungsfälle besser geeignet sein, da er das untergeordnete Element von seinen unmittelbaren Vorgängern entkoppelt. Zusammensetzbare Vorgängerfunktionen werden in der Regel komplexer, um flexiblere zusammensetzbare Funktionen auf niedrigerer Ebene zu ermöglichen.
Ebenso können @Composable-Inhaltslambdas auf dieselbe Weise verwendet werden, um dieselben Vorteile zu erzielen:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Aufbau eines Designs in Compose
- Ansichten in Compose verwenden
- Kotlin für Jetpack Compose