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.
Einführung in 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-Funktion 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. Damit können Sie benannte Objekte mit Baumumfang erstellen, 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 im Material Design verwendet.
MaterialTheme ist ein Objekt, das drei CompositionLocal-Instanzen bereitstellt: colorScheme, typography und shapes. Sie können sie später in jedem untergeordneten Teil der Komposition abrufen.
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 eine CompositionLocal bereitstellen möchten, verwenden Sie die CompositionLocalProvider-Funktion und ihre Infix-Funktion provides, die einen CompositionLocal-Schlüssel mit einer value verknüpft. Die content-Lambdafunktion des CompositionLocalProvider erhält den angegebenen 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 LocalContentColor CompositionLocal. Diese enthält die bevorzugte Inhaltsfarbe für Text und Symbole, 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") }
CompositionLocalExampleIm 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 des 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. Wenn diese Zwischenschichten darüber informiert würden, würde die Nützlichkeit der zusammensetzbaren Funktion eingeschränkt. 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 dieses zusätzlichen Kontexts, der aus der Umgebung verwendet wird, nicht bewusst sein.
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 zusammensetzbaren 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 das Compose-Layoutprüftool liefern genügend Informationen, um dieses Problem zu beheben.
Entscheiden, ob Sie CompositionLocal verwenden möchten
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 kein Standardwert angegeben wird, kann das zu Problemen und Frustration führen, wenn Tests erstellt oder eine Composable-Funktion, die diesen CompositionLocal verwendet, in der Vorschau angezeigt wird. In diesem Fall muss der Wert immer explizit angegeben werden.
Vermeiden Sie CompositionLocal für Konzepte, die nicht als baumbezogen oder unterhierarchiebezogen angesehen werden. Ein CompositionLocal ist sinnvoll, wenn es potenziell von allen Nachfolgern 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, nur die Informationen an Composables zu übergeben, die sie benötigen, und dabei dem Muster State flows down and events flow up (Status fließt abwärts und Ereignisse fließen aufwärts) zu folgen. Dieser Ansatz macht Ihre Composables wiederverwendbarer und einfacher zu testen.
CompositionLocal erstellen
Es gibt zwei APIs zum Erstellen eines CompositionLocal:
compositionLocalOf: Wenn Sie den während der Neuzusammensetzung angegebenen Wert ändern, wird nur der Inhalt ungültig, der seinencurrent-Wert liest.staticCompositionLocalOf: Im Gegensatz zucompositionLocalOfwerden Lesevorgänge vonstaticCompositionLocalOfnicht von Compose erfasst. Wenn Sie den Wert ändern, wird das gesamtecontent-Lambda, in demCompositionLocalangegeben ist, 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 dargestellt 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 CompositionLocal angeben
Die zusammensetzbare Funktion CompositionLocalProvider bindet Werte an CompositionLocal-Instanzen für die angegebene Hierarchie. Wenn Sie einen neuen Wert für ein CompositionLocal bereitstellen möchten, verwenden Sie die Infix-Funktion provides, mit der ein CompositionLocal-Schlüssel einem value zugeordnet wird:
// 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 Gewohnheit, 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 eine Composable zu übergeben, ist die Inversion of Control. Anstatt dass das untergeordnete Element eine Abhängigkeit aufnimmt, um eine bestimmte Logik auszuführen, ü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 Composables auf niedrigerer Ebene zu ermöglichen.
@Composable-Inhaltslambdas können auf ähnliche 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