CompositionLocal
ist ein Tool zum impliziten Übergeben von Daten durch die Komposition. Auf dieser Seite erfahren Sie im Detail, was ein CompositionLocal
ist, wie Sie Ihr eigenes CompositionLocal
erstellen und ob ein CompositionLocal
eine gute Lösung für Ihren Anwendungsfall ist.
Jetzt neu: CompositionLocal
In der Regel fließen in „Compose“ Daten durch den UI-Baum als Parameter nach unten zu den einzelnen zusammensetzbaren Funktionen. Dadurch werden die Abhängigkeiten einer zusammensetzbaren Funktion explizit. Dies kann jedoch bei Daten, die sehr häufig und häufig verwendet werden, z. B. Farben oder Schriftstile, umständlich sein. Hier ein Beispiel:
@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
an. Damit können Sie baumbasierte benannte Objekte erstellen, die implizit für den Datenfluss durch die UI-Struktur verwendet werden können.
CompositionLocal
-Elemente werden normalerweise mit einem Wert in einem bestimmten Knoten des UI-Baums angegeben. Dieser Wert kann von seinen zusammensetzbaren Nachfolgern verwendet werden, ohne dass CompositionLocal
als Parameter in der zusammensetzbaren Funktion deklariert wird.
CompositionLocal
wird beim Material Theme im Hintergrund verwendet.
MaterialTheme
ist ein Objekt, das drei CompositionLocal
-Instanzen (Farben, Typografie und Formen) bereitstellt, mit denen du diese später in jedem nachfolgenden Teil der Komposition abrufen kannst. Im Einzelnen sind dies die Attribute LocalColors
, LocalShapes
und LocalTypography
, auf die Sie über die Attribute MaterialTheme
, colors
, shapes
und typography
zugreifen können.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colors, 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.colors.primary ) }
Eine CompositionLocal
-Instanz ist einem Teil der Zusammensetzung zugeordnet, sodass Sie auf verschiedenen Baumebenen unterschiedliche Werte angeben können. Der current
-Wert einer CompositionLocal
entspricht dem nächstgelegenen Wert, der von einem Ancestor in diesem Teil der Komposition bereitgestellt wird.
Wenn Sie einem CompositionLocal
einen neuen Wert zuweisen möchten, verwenden Sie den CompositionLocalProvider
und die zugehörige Infix-Funktion provides
, die einen CompositionLocal
-Schlüssel mit einem value
verknüpft. Das Lambda content
des CompositionLocalProvider
erhält den angegebenen Wert, wenn auf das Attribut current
von CompositionLocal
zugegriffen wird. Wenn ein neuer Wert angegeben wird, setzt die Funktion „Compose“ Teile der Zusammensetzung neu zusammen, die die CompositionLocal
lesen.
Beispielsweise enthält der CompositionLocal
LocalContentAlpha
den bevorzugten Alpha-Inhalt für Text und Ikonografie, um verschiedene Teile der Benutzeroberfläche hervorzuheben oder abzuschwächen. Im folgenden Beispiel wird CompositionLocalProvider
verwendet, um verschiedene Werte für verschiedene Teile der Komposition bereitzustellen.
@Composable fun CompositionLocalExample() { MaterialTheme { // MaterialTheme sets ContentAlpha.high as default Column { Text("Uses MaterialTheme's provided alpha") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text("Medium value provided for LocalContentAlpha") Text("This Text also uses the medium value") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { DescendantExample() } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the disabled alpha now") }
Abbildung 1: Vorschau der zusammensetzbaren Funktion CompositionLocalExample
.
In allen obigen Beispielen wurden die CompositionLocal
-Instanzen intern von Material-Zusammensetzbaren verwendet. Verwenden Sie das Attribut current
, um auf den aktuellen Wert einer CompositionLocal
zuzugreifen. Im folgenden Beispiel wird der aktuelle Context
-Wert des LocalContext
-CompositionLocal
, der häufig in Android-Apps verwendet wird, zur Formatierung des Textes 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 über die Zusammensetzung.
Ein weiteres wichtiges Signal für die Verwendung von CompositionLocal
besteht darin, dass der Parameter Querschnittsschichten ist und Zwischenschichten der Implementierung nicht erkennen sollten, dass sie existiert, da die Kenntnis dieser Zwischenschichten den Nutzen der zusammensetzbaren Funktion einschränken würde. Beispielsweise ermöglicht ein CompositionLocal
-Objekt das Abfragen von Android-Berechtigungen. Eine zusammensetzbare Medienauswahl kann neue Funktionen für den Zugriff auf berechtigungsgeschützte Inhalte auf dem Gerät hinzufügen, ohne die API zu ändern. Außerdem müssen Aufrufer der Medienauswahl über diesen zusätzlichen Kontext aus der Umgebung informiert sein.
CompositionLocal
ist jedoch nicht immer die beste Lösung. Wir raten von der übermäßigen Verwendung von CompositionLocal
ab, da dies einige Nachteile mit sich bringt:
Mit CompositionLocal
ist das Verhalten einer zusammensetzbaren Funktion schwerer verständlich. Beim Erstellen impliziter Abhängigkeiten müssen Aufrufer von zusammensetzbaren Funktionen, die diese verwenden, dafür sorgen, dass ein Wert für jede CompositionLocal
erfüllt ist.
Außerdem gibt es für diese Abhängigkeit möglicherweise keine eindeutige Quelle der Wahrheit, da sie in jedem Teil der Zusammensetzung mutieren kann. Daher kann das Debuggen der Anwendung bei einem Problem schwieriger sein, da Sie in der Zusammensetzung nach oben gehen müssen, um zu sehen, wo der Wert current
angegeben wurde. Tools wie Nutzungen suchen in der IDE oder der Compose Layout Inspector bieten genügend Informationen, um dieses Problem zu beheben.
Entscheiden, ob CompositionLocal
verwendet werden soll
Wenn CompositionLocal
eine gute Lösung für Ihren Anwendungsfall ist, gelten bestimmte Bedingungen:
CompositionLocal
sollte einen guten Standardwert haben. Falls kein Standardwert vorhanden ist, muss sichergestellt werden, dass es für einen Entwickler sehr schwierig ist, in eine Situation zu kommen, in der kein Wert für CompositionLocal
angegeben ist.
Wenn kein Standardwert angegeben wird, kann es beim Erstellen von Tests oder bei der Vorschau einer zusammensetzbaren Funktion, die diesen CompositionLocal
verwendet, zu Problemen und Frustration kommen.
Vermeiden Sie CompositionLocal
für Konzepte, die nicht auf Baum- oder Unterhierarchieebene ausgelegt sind. Ein CompositionLocal
ist sinnvoll, wenn sie potenziell von einem Nachfolgerelement und nicht von einigen wenigen von ihnen verwendet werden kann.
Wenn Ihr Anwendungsfall diese Anforderungen nicht erfüllt, sehen Sie sich den Abschnitt Alternativen an, bevor Sie eine CompositionLocal
erstellen.
Ein Beispiel für eine schlechte Praxis ist das Erstellen eines CompositionLocal
-Objekts, das den ViewModel
eines bestimmten Bildschirms enthält, sodass alle zusammensetzbaren Funktionen in diesem Bildschirm einen Verweis auf die ViewModel
erhalten können, um eine Logik auszuführen. Dies ist nicht empfehlenswert, da nicht alle zusammensetzbaren Funktionen unterhalb einer bestimmten UI-Struktur über eine ViewModel
informiert werden müssen. Es empfiehlt sich, nur die Informationen an zusammensetzbare Funktionen weiterzugeben, die dem Muster folgen, das den Zustand nach unten und die Ereignisse nach oben. Dieser Ansatz macht Ihre zusammensetzbaren Funktionen wiederverwendbarer und einfacher zu testen.
CompositionLocal
wird erstellt
CompositionLocal
kann mit zwei APIs erstellt werden:
compositionLocalOf
: Wenn Sie den bei der Neuzusammensetzung angegebenen Wert ändern, wird nur der Inhalt ungültig, der seinencurrent
-Wert liest.staticCompositionLocalOf
: Im Gegensatz zucompositionLocalOf
werden Lesevorgänge einesstaticCompositionLocalOf
-Objekts nicht von der Funktion "Compose" erfasst. Durch das Ändern des Werts wird die gesamte Lambda-Funktioncontent
, an derCompositionLocal
angegeben ist, und nicht nur die Stellen, an denen dercurrent
-Wert in der Zusammensetzung gelesen wird, neu zusammengesetzt.
Wenn es höchst unwahrscheinlich ist, dass sich der für CompositionLocal
angegebene Wert ändert oder nie ändert, verwenden Sie staticCompositionLocalOf
, um Leistungsvorteile zu erzielen.
Beispielsweise könnte das Designsystem einer App so strukturiert sein, wie zusammensetzbare Funktionen durch einen Schatten für die UI-Komponente erhöht werden. Da die verschiedenen Höhen für die Anwendung im gesamten UI-Baum verteilt werden sollen, verwenden wir CompositionLocal
. Da der Wert CompositionLocal
basierend auf dem Systemthema bedingt 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 einem CompositionLocal
einen neuen Wert zuweisen möchten, verwenden Sie die Infix-Funktion provides
, die einen CompositionLocal
-Schlüssel so mit einer 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, das einen Wert für diese CompositionLocal
bereitstellt:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition Card(elevation = LocalElevations.current.card) { // Content } }
Alternativen
Ein CompositionLocal
kann für einige Anwendungsfälle eine übermäßige Lösung sein. Wenn Ihr Anwendungsfall die im Abschnitt Entscheiden, ob CompositionLocal zu verwenden angegebenen Kriterien nicht erfüllt, ist möglicherweise eine andere Lösung für Ihren Anwendungsfall möglicherweise besser geeignet.
Explizite Parameter übergeben
Es ist eine gute Gewohnheit, die Abhängigkeiten von zusammensetzbaren Funktionen explizit anzugeben. Wir empfehlen, zusammensetzbare Funktionen nur zu übergeben, was sie benötigen. Um das Entkoppeln und Wiederverwenden von zusammensetzbaren Funktionen zu fördern, sollte jede zusammensetzbare Funktion 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 }
Umkehrung der Kontrolle
Eine weitere Möglichkeit, die Übergabe unnötiger Abhängigkeiten an eine zusammensetzbare Funktion zu vermeiden, ist die Umkehrung der Kontrolle. Anstatt dass der Nachfolger eine Abhängigkeit übernimmt, um eine Logik auszuführen, führt dies stattdessen das übergeordnete Element aus.
Im folgenden Beispiel muss ein Nachfolger die Anfrage zum Laden einiger 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 trägt MyDescendant
möglicherweise eine hohe Verantwortung. Außerdem macht das Übergeben von MyViewModel
als Abhängigkeit die Wiederverwendbarkeit von MyDescendant
geringer, da die beiden jetzt miteinander gekoppelt sind. Betrachten Sie die Alternative, die die Abhängigkeit nicht an das untergeordnete Element übergibt und eine Umkehrung der Steuerungsprinzipien verwendet, wodurch der Ancestor 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 eignet sich für einige Anwendungsfälle besser, da das untergeordnete Element von seinen unmittelbaren Ancestors entkoppelt wird. Ancestors werden in der Regel komplexer, da sie flexiblere untergeordnete Komponenten bieten.
In ähnlicher Weise können @Composable
-Inhalts-Lambdas auf dieselbe Weise verwendet werden, um dieselben Vorteile zu erhalten:
@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: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Aufbau eines Designs in der Funktion „Compose“
- Ansichten in Compose verwenden
- Kotlin für Jetpack Compose