CompositionLocal
ist ein Tool zum impliziten Übergeben von Daten durch die Komposition. Auf dieser Seite erfahren Sie mehr über CompositionLocal
, wie Sie eigene CompositionLocal
erstellen und ob ein CompositionLocal
eine gute Lösung für Ihren Anwendungsfall ist.
Jetzt neu: CompositionLocal
Normalerweise fließen Daten in Compose abwärts durch den UI-Baum als Parameter für jede zusammensetzbare Funktion. Dadurch werden die Abhängigkeiten einer Composable explizit. Bei Daten, die sehr häufig und weit verbreitet verwendet werden, z. B. Farben oder Schriftarten, kann dies jedoch umständlich sein. Sehen Sie sich folgendes 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 Composables übergeben werden müssen, bietet Compose CompositionLocal
, mit dem Sie benannte Objekte mit Baumumfang erstellen können, die als implizite Möglichkeit verwendet werden können, Daten durch den UI-Baum fließen zu lassen.
CompositionLocal
-Elemente haben in der Regel einen Wert in einem bestimmten Knoten des UI-Baums. Dieser Wert kann von den zusammensetzbaren Nachfolgern verwendet werden, ohne dass CompositionLocal
als Parameter in der zusammensetzbaren Funktion deklariert werden muss.
CompositionLocal
wird intern vom Material-Design verwendet.
MaterialTheme
ist ein Objekt, das drei CompositionLocal
-Instanzen bereitstellt: colorScheme
, typography
und shapes
. Sie können später in jedem untergeordneten Teil der Komposition abgerufen werden.
Das sind die Attribute LocalColorScheme
, LocalShapes
und LocalTypography
, auf die Sie über die Attribute MaterialTheme
, colorScheme
, shapes
und typography
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. So können Sie auf verschiedenen Ebenen des Baums unterschiedliche Werte angeben. Der current
-Wert eines CompositionLocal
entspricht dem nächstgelegenen Wert, der von einem übergeordneten Element in diesem Teil der Komposition bereitgestellt wird.
Wenn Sie einen neuen Wert für ein CompositionLocal
bereitstellen möchten, verwenden Sie die CompositionLocalProvider
-Funktion und ihre Infix-Funktion provides
, die einen CompositionLocal
-Schlüssel einem value
zuordnet. Die content
-Lambdafunktion des CompositionLocalProvider
erhält den bereitgestellten Wert, wenn auf die current
-Eigenschaft des CompositionLocal
zugegriffen wird. Wenn ein neuer Wert angegeben wird, führt Compose eine Neukomposition von Teilen der Komposition durch, in denen CompositionLocal
gelesen wird.
Ein Beispiel dafür ist die LocalContentColor
CompositionLocal
, die die bevorzugte Inhaltsfarbe für Text und Symbole enthält, damit sie sich vom aktuellen Hintergrund abhebt. Im folgenden Beispiel wird CompositionLocalProvider
verwendet, um verschiedene Werte für verschiedene Teile der Komposition 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") }
Abbildung 1: Vorschau der zusammensetzbaren Funktion CompositionLocalExample
.
Im letzten Beispiel wurden die CompositionLocal
-Instanzen intern von Material-Composables verwendet. Wenn Sie auf den aktuellen Wert eines CompositionLocal
zugreifen möchten, verwenden Sie das Attribut current
. Im folgenden Beispiel wird der aktuelle Context
-Wert von LocalContext
CompositionLocal
, der häufig 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 Zwischenschichten der Implementierung nicht wissen sollten, dass er vorhanden ist, da dies die Nützlichkeit der zusammensetzbaren Funktion einschränken würde. Für die Abfrage von Android-Berechtigungen wird beispielsweise im Hintergrund ein CompositionLocal
verwendet. Ein Media Picker-Composable kann neue Funktionen hinzufügen, um auf berechtigungsgeschützte Inhalte auf dem Gerät zuzugreifen, ohne die API zu ändern. Aufrufer des Media Pickers müssen sich nicht des zusätzlichen Kontexts bewusst sein, 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 es, das Verhalten einer Composable-Funktion nachzuvollziehen. Da sie implizite Abhängigkeiten erstellen, müssen Aufrufer von Composables, die sie verwenden, dafür sorgen, dass ein Wert für jedes CompositionLocal
angegeben wird.
Außerdem gibt es möglicherweise keine eindeutige „Source of Truth“ 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 angegeben wurde. Tools wie Verwendungen suchen in der IDE oder der Compose-Layoutinspektor liefern 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:
Ein CompositionLocal
sollte einen guten Standardwert haben. Wenn es keinen Standardwert gibt, müssen Sie dafür sorgen, dass es für Entwickler äußerst schwierig ist, in eine Situation zu geraten, in der kein Wert für CompositionLocal
angegeben wird.
Wenn Sie keinen Standardwert angeben, kann das zu Problemen und Frustration führen, wenn Sie Tests erstellen oder eine Komponente in der Vorschau ansehen, die CompositionLocal
verwendet. In diesem Fall muss der Wert immer explizit angegeben werden.
Vermeiden Sie CompositionLocal
für Konzepte, die nicht als baum- oder unterhierarchiebezogen gelten. Ein CompositionLocal
ist sinnvoll, wenn es potenziell von allen untergeordneten Elementen verwendet werden kann, nicht nur von einigen.
Wenn Ihr Anwendungsfall diese Anforderungen nicht erfüllt, lesen Sie den Abschnitt Alternativen, bevor Sie ein CompositionLocal
erstellen.
Ein Beispiel für eine schlechte Vorgehensweise ist das Erstellen eines CompositionLocal
, das das ViewModel
eines bestimmten Bildschirms enthält, sodass alle Composables auf diesem Bildschirm einen Verweis auf das ViewModel
erhalten können, um eine bestimmte Logik auszuführen. Das ist keine gute Vorgehensweise, da nicht alle Composables unter einem bestimmten UI-Baum über ein ViewModel
informiert sein müssen. Es empfiehlt sich, Composables nur die Informationen zu übergeben, die sie benötigen, und dem Muster zu folgen, dass der Status nach unten und Ereignisse nach oben fließen. So werden Ihre Composables wiederverwendbarer und lassen sich leichter testen.
CompositionLocal
erstellen
Es gibt zwei APIs zum Erstellen eines CompositionLocal
:
compositionLocalOf
: Wenn Sie den Wert ändern, der während der Neuzusammensetzung angegeben wird, wird nur der Inhalt ungültig, der seinencurrent
-Wert liest.staticCompositionLocalOf
: Im Gegensatz zucompositionLocalOf
werden Lesevorgänge einesstaticCompositionLocalOf
nicht von Compose erfasst. Wenn Sie den Wert ändern, wird das gesamtecontent
-Lambda, in demCompositionLocal
bereitgestellt wird, neu zusammengesetzt. Das gilt nicht nur für die Stellen, an denen dercurrent
-Wert in der Komposition gelesen wird.
Wenn sich der für CompositionLocal
angegebene Wert sehr wahrscheinlich nicht oder nie ändert, verwenden Sie staticCompositionLocalOf
, um die Leistung zu verbessern.
Das Designsystem einer App kann beispielsweise vorgeben, wie Composables mithilfe eines Schattens für die UI-Komponente erhöht werden. Da sich die verschiedenen Erhebungen für die App im gesamten UI-Baum fortsetzen sollen, verwenden wir ein CompositionLocal
. Da der Wert CompositionLocal
bedingt auf Grundlage des Systemdesigns 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 eine CompositionLocal
angeben
Die zusammensetzbare Funktion CompositionLocalProvider
bindet Werte an CompositionLocal
-Instanzen für die angegebene Hierarchie. Wenn Sie einen neuen Wert für eine CompositionLocal
bereitstellen möchten, verwenden Sie die Infix-Funktion provides
, die einen CompositionLocal
-Schlüssel mit einem value
verknüpft:
// 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
nutzen
CompositionLocal.current
gibt den Wert zurück, der vom nächsten CompositionLocalProvider
bereitgestellt wird, der einen Wert für diesen 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
Ein CompositionLocal
ist für einige Anwendungsfälle möglicherweise eine überzogene Lösung. Wenn Ihr Anwendungsfall nicht den Kriterien im Abschnitt Entscheiden, ob CompositionLocal verwendet werden soll entspricht, ist wahrscheinlich eine andere Lösung besser geeignet.
Explizite Parameter übergeben
Es ist eine gute Angewohnheit, die Abhängigkeiten von zusammensetzbaren Funktionen explizit anzugeben. Wir empfehlen, Composable-Funktionen nur das zu übergeben, was sie benötigen. Um die Entkopplung und Wiederverwendung von Composables zu fördern, sollte jedes Composable so wenig 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 ein Composable zu übergeben, ist die Inversion of Control. Anstatt dass das untergeordnete Element eine Abhängigkeit für die Ausführung einer bestimmten Logik übernimmt, übernimmt das übergeordnete Element diese Aufgabe.
Im folgenden Beispiel muss ein untergeordnetes Element die Anfrage zum Laden von Daten auslösen:
@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
eine große Verantwortung haben. Außerdem wird MyDescendant
weniger wiederverwendbar, wenn MyViewModel
als Abhängigkeit übergeben wird, da die beiden jetzt gekoppelt sind. Sehen Sie sich die Alternative an, bei der die Abhängigkeit nicht an den Nachfolger übergeben wird und die Inversion-of-Control-Prinzipien verwendet werden, wodurch der Vorfahre 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 übergeordneten Elementen entkoppelt. Ancestor-Composables werden in der Regel komplexer, um flexiblere Low-Level-Composables zu ermöglichen.
@Composable
-Inhaltslambdas können 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 dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Aufbau eines Designs in Compose
- Ansichten in Compose verwenden
- Kotlin für Jetpack Compose