CompositionLocal
ist ein Tool, mit dem Daten implizit über die Komposition weitergegeben werden. Auf dieser Seite erfahren Sie mehr darüber, was eine CompositionLocal
ist, wie Sie Ihre eigene CompositionLocal
erstellen und ob eine CompositionLocal
eine gute Lösung für Ihren Anwendungsfall ist.
Jetzt neu: CompositionLocal
Normalerweise fließen Daten in Compose als Parameter durch den UI-Baum hinunter zu jeder zusammensetzbaren Funktion. Dadurch werden die Abhängigkeiten eines Composeables explizit. Bei Daten, die sehr häufig und weit verbreitet sind, wie Farben oder Schriftstile, 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 Compose-Elemente übergeben werden müssen, bietet Compose CompositionLocal
, mit dem Sie benannte Objekte auf Baumebene erstellen können, die als implizite Methode zum Durchleiten von Daten durch den UI-Baum verwendet werden können.
CompositionLocal
-Elemente werden in der Regel in einem bestimmten Knoten des UI-Baums mit einem Wert versehen. Dieser Wert kann von den übergeordneten Funktionen verwendet werden, ohne dass CompositionLocal
als Parameter in der zusammensetzbaren Funktion deklariert werden muss.
Im Hintergrund wird CompositionLocal
verwendet.
MaterialTheme
ist ein Objekt mit drei CompositionLocal
-Instanzen: colorScheme
, typography
und shapes
. Sie können sie später in einem beliebigen 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, sodass Sie auf verschiedenen Ebenen des Baums unterschiedliche Werte angeben können. Der current
-Wert eines CompositionLocal
entspricht dem Wert, der dem Element am nächsten ist, der von einem übergeordneten Element in diesem Teil der Komposition bereitgestellt wird.
Wenn Sie einem CompositionLocal
einen neuen Wert zuweisen möchten, verwenden Sie die Funktion CompositionLocalProvider
und ihre Infixfunktion provides
, die einem CompositionLocal
-Schlüssel einen value
-Wert zuordnet. Die content
-Lambda-Funktion der CompositionLocalProvider
erhält den angegebenen Wert, wenn auf die current
-Property der CompositionLocal
zugegriffen wird. Wenn ein neuer Wert angegeben wird, setzt „Compose“ Teile der Komposition neu zusammen, die CompositionLocal
lesen.
Beispielsweise enthält LocalContentColor
CompositionLocal
die bevorzugte Inhaltsfarbe, die für Text und Symbole verwendet wird, damit sie sich von der aktuellen Hintergrundfarbe abhebt. Im folgenden Beispiel wird CompositionLocalProvider
verwendet, um verschiedene Werte für verschiedene Teile der Komposition anzugeben.
@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-Kompositen verwendet. Wenn Sie auf den aktuellen Wert eines CompositionLocal
zugreifen möchten, verwenden Sie die Property current
. Im folgenden Beispiel wird der aktuelle Context
-Wert des LocalContext
CompositionLocal
verwendet, der häufig in Android-Apps verwendet wird, um den Text zu formatieren:
@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 Weitergeben von Daten durch die Komposition.
Ein weiteres wichtiges Signal für die Verwendung von CompositionLocal
ist, wenn der Parameter schnittstellenübergreifend ist und Zwischenschichten der Implementierung nicht wissen sollten, dass er existiert, da dies die Nützlichkeit der Composeable-Funktion einschränken würde. Zum Beispiel ermöglicht die Abfrage von Android-Berechtigungen eine CompositionLocal
im Hintergrund. Eine zusammensetzbare Media-Auswahl 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 die Aufrufer der Media-Auswahl über diesen zusätzlichen Kontext, der aus der Umgebung verwendet wird, informiert sein.
CompositionLocal
ist jedoch nicht immer die beste Lösung. Wir raten davon ab, CompositionLocal
zu häufig zu verwenden, da dies einige Nachteile hat:
CompositionLocal
erschwert es, das Verhalten eines komponierbaren Elements zu verstehen. Da sie implizite Abhängigkeiten schaffen, müssen die Aufrufer von Composables, die sie verwenden, dafür sorgen, dass für jede CompositionLocal
ein Wert festgelegt ist.
Außerdem gibt es möglicherweise keine eindeutige „Source of Truth“ für diese Abhängigkeit, da sie in jedem Teil der Komposition mutieren kann. Daher kann es schwieriger sein, die App zu debuggen, wenn ein Problem auftritt, da Sie sich durch die Komposition bewegen müssen, um zu sehen, wo der current
-Wert angegeben wurde. Tools wie Verwendungsfälle finden in der IDE oder der Layout-Inspektor für Compose liefern ausreichend 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 kein Standardwert vorhanden ist, müssen Sie darauf achten, dass es für einen Entwickler sehr schwierig ist, in eine Situation zu geraten, in der kein Wert für CompositionLocal
angegeben ist.
Wenn Sie keinen Standardwert angeben, kann das beim Erstellen von Tests oder bei der Vorschau eines Composeables, in dem dieser Wert verwendet wird, zu Problemen führen. CompositionLocal
muss dann immer explizit angegeben werden.
Verwenden Sie CompositionLocal
nicht für Konzepte, die nicht auf Baum- oder untergeordnete Hierarchieebene beschränkt sind. Eine CompositionLocal
ist sinnvoll, wenn sie 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 eine CompositionLocal
erstellen.
Ein Beispiel für eine schlechte Praxis ist das Erstellen einer CompositionLocal
, die die ViewModel
eines bestimmten Bildschirms enthält, damit alle Composeables auf diesem Bildschirm einen Verweis auf die ViewModel
erhalten können, um eine Logik auszuführen. Das ist nicht empfehlenswert, da nicht alle Composeables unter einem bestimmten UI-Baum eine ViewModel
kennen müssen. Es empfiehlt sich, den Composeables nur die Informationen 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 die zusammensetzbaren Funktionen wiederverwendbar und lassen sich leichter testen.
CompositionLocal
erstellen
Es gibt zwei APIs, mit denen du eine CompositionLocal
erstellen kannst:
compositionLocalOf
: Wenn Sie den während der Neuzusammensetzung angegebenen Wert ändern, wird nur der Inhalt ungültig, in dem der Wertcurrent
gelesen wird.staticCompositionLocalOf
: Im Gegensatz zucompositionLocalOf
werden Lesevorgänge vonstaticCompositionLocalOf
von Compose nicht erfasst. Wenn Sie den Wert ändern, wird die gesamtecontent
-Lambda-Funktion, in der derCompositionLocal
-Wert angegeben 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
bereitgestellte Wert sehr unwahrscheinlich oder nie ändern wird, verwenden Sie staticCompositionLocalOf
, um Leistungsvorteile zu erhalten.
Beispielsweise kann das Designsystem einer App anders formuliert sein, um zusammensetzbare Funktionen mit einem Schatten für die UI-Komponente zu optimieren. Da die verschiedenen Ebenen der App im gesamten UI-Baum übernommen werden sollen, verwenden wir eine CompositionLocal
. Da der Wert für CompositionLocal
bedingt vom Systemthema 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
Das CompositionLocalProvider
-kompositum bindet Werte an CompositionLocal
-Instanzen für die angegebene Hierarchie. Wenn Sie einer CompositionLocal
einen neuen Wert zuweisen möchten, verwenden Sie die Infixfunktion provides
, um einen CompositionLocal
-Schlüssel einem value
zuzuweisen:
// 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 des nächstgelegenen CompositionLocalProvider
zurück, der einen Wert für diesen CompositionLocal
angibt:
@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 } }
Mögliche Alternativen
Für einige Anwendungsfälle ist eine CompositionLocal
möglicherweise eine übertriebene Lösung. Wenn Ihr Anwendungsfall nicht die im Abschnitt Entscheidung, ob „CompositionLocal“ verwendet werden soll angegebenen Kriterien erfüllt, ist eine andere Lösung wahrscheinlich besser für Ihren Anwendungsfall geeignet.
Explizite Parameter übergeben
Es ist eine gute Angewohnheit, die Abhängigkeiten von Composeable-Objekten explizit anzugeben. Wir empfehlen, den Composeable-Elementen nur das zu übergeben, was sie benötigen. Um die Entkopplung und Wiederverwendung von Composeable-Elementen zu fördern, sollte jedes Composeable-Element möglichst wenig Informationen 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, 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 Logik übernimmt, übernimmt das übergeordnete Element dies stattdessen.
Im folgenden Beispiel muss ein untergeordneter Knoten 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
viel Verantwortung übernehmen. Außerdem wird durch die Übergabe von MyViewModel
als Abhängigkeit die Wiederverwendbarkeit von MyDescendant
verringert, da die beiden jetzt miteinander gekoppelt sind. Betrachten Sie die Alternative, bei der die Abhängigkeit nicht an den untergeordneten Knoten übergeben wird und die Prinzipien der Inversion of Control verwendet, die den übergeordneten Knoten für die Ausführung der Logik verantwortlich machen:
@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 das untergeordnete Element von seinen unmittelbaren Vorfahren getrennt wird. Übergeordnete Composeables werden in der Regel komplexer, um flexiblere Composeables auf niedrigerer Ebene zu ermöglichen.
In ähnlicher Weise können @Composable
-Lambdas für Inhalte auf die gleiche 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 Themas in Compose
- Ansichten in der compose-Ansicht verwenden
- Kotlin für Jetpack Compose