Daten auf lokaler Ebene mit CompositionLocal

CompositionLocal ist ein Tool für Daten implizit durch die Komposition weitergeleitet werden. Auf dieser Seite werden Sie mehr über CompositionLocal erfahren, wie Sie eigene erstellen CompositionLocal. Wenn CompositionLocal eine gute Lösung ist, Ihren Anwendungsfall.

Jetzt neu: CompositionLocal

Normalerweise fließen Daten beim Schreiben durch die UI-Baum als Parameter für jede zusammensetzbare Funktion Dadurch wird das explizit sind. Dies kann bei Daten, die sehr umfangreich sind, jedoch umständlich sein. häufig und weit verbreitet, wie Farben oder Schriftstile. Weitere Informationen: 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
    )
}

Um zu unterstützen, dass die Farben nicht als explizite Parameterabhängigkeit an Mit der Option Compose AngeboteCompositionLocal können Sie zum Erstellen von benannten Objekten auf Baumebene, die implizit verwendet werden können, um Datenfluss durch den UI-Baum.

CompositionLocal-Elemente werden normalerweise mit einem Wert in einem bestimmten Knoten bereitgestellt des UI-Baums. Dieser Wert kann von seinen zusammensetzbaren Nachfolgerelementen verwendet werden, ohne Sie legen CompositionLocal als Parameter in der zusammensetzbaren Funktion fest.

Im Hintergrund wird CompositionLocal verwendet. MaterialTheme ist Ein Objekt, das drei CompositionLocal-Instanzen bereitstellt – Farben, Typografie und Formen erstellen, sodass Sie sie später in allen untergeordneten Elementen Komposition Im Einzelnen sind dies die LocalColors, LocalShapes und LocalTypography-Properties, auf die du über die MaterialTheme zugreifen kannst colors, shapes und typography.

@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 auf einen Teil der Komposition beschränkt, sodass Sie können verschiedene Werte auf verschiedenen Ebenen der Baumstruktur bereitgestellt werden. Der Wert current einer CompositionLocal entspricht dem nächstgelegenen Wert, der von einem in diesem Teil der Komposition.

Um einen neuen Wert für CompositionLocal festzulegen, verwenden Sie die Methode CompositionLocalProvider und dessen provides infix-Funktion, die einen CompositionLocal-Schlüssel mit value verknüpft. Die content Lambda von CompositionLocalProvider wird bereitgestellt -Wert beim Zugriff auf die current-Eigenschaft von CompositionLocal. Wenn ein wird ein neuer Wert angegeben, setzt "Compose" Teile der Komposition neu zusammen, CompositionLocal.

Ein Beispiel: Die CompositionLocal von LocalContentAlpha enthält das bevorzugte Alpha-Element für Text und Symbole, um verschiedene Teile der Benutzeroberfläche zu betonen oder abzuschwächen. Im Im folgenden Beispiel wird CompositionLocalProvider verwendet, für verschiedene Teile der Komposition.

@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 verwendet durch „Material Componables“. So greifen Sie auf den aktuellen Wert einer CompositionLocal zu: Verwenden Sie die current Property. Im folgenden Beispiel wird der aktuelle Wert Context von LocalContext CompositionLocal, das häufig in Android-Apps verwendet wird, dient der Formatierung Text:

@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)
}

Eigenes CompositionLocal erstellen

CompositionLocal ist ein Tool zum Übergeben von Daten durch die Komposition implizit.

Ein weiteres wichtiges Signal für die Verwendung von CompositionLocal ist, dass der Parameter übergreifende und Zwischenschichten der Implementierung nicht wissen, vorhanden ist, da die Erkennung dieser Zwischenebenen den der zusammensetzbaren Funktion. Die Abfrage von Android-Berechtigungen was sich CompositionLocal unter der Haube bietet. Eine zusammensetzbare Media-Auswahl neue Funktionen für den Zugriff auf berechtigungsgeschützte Inhalte auf der Gerät verwenden, ohne die API zu ändern und die Aufrufer der Media-Auswahl berücksichtigen Sie diesen zusätzlichen Kontext, der aus der Umgebung verwendet wird.

CompositionLocal ist jedoch nicht immer die beste Lösung. Mi. Vermeide eine zu häufige Verwendung von CompositionLocal, da dies einige Nachteile hat:

Mit CompositionLocal lässt sich das Verhalten einer zusammensetzbaren Funktion erschweren. Als erstellen implizite Abhängigkeiten, die Aufrufer von zusammensetzbaren Funktionen, die diese verwenden, um sicherzustellen, dass ein Wert für jeden CompositionLocal erfüllt ist.

Darüber hinaus gibt es möglicherweise keine eindeutige Datenquelle für diese Abhängigkeit, da sie können an einem beliebigen Teil der Komposition angepasst werden. Das Debugging der App auftreten, kann schwieriger sein, da Sie die Zusammensetzung, um zu sehen, wo der Wert current angegeben wurde. Tools wie Suchen Nutzungen in der IDE oder im Compose layout Inspector (Layout-Inspektor verfassen), um genügend Informationen dieses Problem zu beheben.

Entscheiden, ob CompositionLocal verwendet werden soll

CompositionLocal kann eine gute Lösung sein, wenn bestimmte Bedingungen erfüllt sind. für Ihren Anwendungsfall:

Ein CompositionLocal sollte einen guten Standardwert haben. Wenn kein Standardwert vorhanden ist müssen Sie garantieren, dass es für einen Entwickler außerordentlich schwierig ist, für den Fall, dass für CompositionLocal kein Wert angegeben ist. Wenn Sie keinen Standardwert angeben, kann dies zu Problemen und Frust beim Erstellen oder eine Vorschau einer zusammensetzbaren Funktion mit dieser CompositionLocal-Funktion ausführen, explizit angegeben werden.

Vermeiden Sie CompositionLocal für Konzepte, die nicht als baumbezogen oder auf Ebene der untergeordneten Hierarchie. Ein CompositionLocal ist sinnvoll, wenn es möglicherweise von allen Nachfolgern verwendet werden, nicht von einigen wenigen.

Wenn Ihr Anwendungsfall diese Anforderungen nicht erfüllt, sehen Sie sich die Abschnitt Alternativen, bevor Sie eine CompositionLocal

Ein Beispiel für eine unzulässige Vorgehensweise ist das Erstellen einer CompositionLocal, die den ViewModel eines bestimmten Bildschirms, sodass alle zusammensetzbaren Funktionen in diesem Bildschirm einen Verweis auf das ViewModel abzurufen, um eine Logik auszuführen. Das ist nicht empfehlenswert da nicht alle zusammensetzbaren Funktionen unter einem bestimmten UI-Baum Informationen zum ViewModel Es hat sich bewährt, nur die Informationen dass sie dem Muster folgen müssen, dass der Zustand nach unten und Ereignisse nach oben fließen. Mit diesem Ansatz werden Ihre zusammensetzbaren Funktionen wiederverwendbar und leichter zu testen.

CompositionLocal wird erstellt

Es gibt zwei APIs zum Erstellen einer CompositionLocal:

  • compositionLocalOf: Wenn der Wert während der Neuzusammensetzung geändert wird, werden nur ungültig. den Inhalt, der seine current Wert.

  • staticCompositionLocalOf: Im Gegensatz zu compositionLocalOf sind Lesevorgänge einer staticCompositionLocalOf nicht von Compose verfolgt. Wenn Sie den Wert ändern, wird der Wert für content vollständig Lambda-Funktion, bei der das CompositionLocal zur Neuzusammensetzung bereitgestellt wird, nur die Stellen, an denen der current-Wert in der Zusammensetzung gelesen wird.

Es ist sehr unwahrscheinlich, dass sich der für CompositionLocal angegebene Wert ändert oder ändert sich nichts. Verwenden Sie staticCompositionLocalOf, um die Leistung zu verbessern.

Beispielsweise kann das Designsystem einer App anders formuliert sein, werden durch einen Schatten für die UI-Komponente erhöht. Da die unterschiedlichen Höhen für die Anwendung sollten im gesamten UI-Baum verteilt werden. Wir verwenden einen CompositionLocal Da der Wert CompositionLocal 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 CompositionLocal angeben

Das CompositionLocalProvider zusammensetzbare Funktion bindet Werte an CompositionLocal-Instanzen für die Hierarchie. Um einen neuen Wert für CompositionLocal festzulegen, verwenden Sie die Methode provides infix-Funktion, die wie folgt 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 des nächstgelegenen CompositionLocalProvider zurück, 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
    Card(elevation = LocalElevations.current.card) {
        // Content
    }
}

Mögliche Alternativen

CompositionLocal kann für manche Anwendungsfälle eine übermäßige Lösung sein. Wenn Ihr Anwendungsfall nicht die Kriterien erfüllt, die unter Verwendung CompositionLocal angezeigt wird, könnte eine andere Lösung wahrscheinlich besser sein die für Ihren Anwendungsfall geeignet sind.

Explizite Parameter übergeben

Es ist eine gute Gewohnheit, die Abhängigkeiten der zusammensetzbaren Funktion explizit anzugeben. Wir empfehlen, Sie zusammensetzbare Funktionen nur das übergeben, was sie benötigen. Um die Entkopplung zu fördern, und Wiederverwendung von zusammensetzbaren Funktionen ist, sollte jede zusammensetzbare Funktion die geringste Menge an Informationen möglich sind.

@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, durch die Kontrollumkehr. Anstatt eine Abhängigkeit vom Nachfolger Logik ausführen, macht das übergeordnete Element dies stattdessen.

Im folgenden Beispiel wird gezeigt, wie ein Nachfolger die Anfrage auslösen muss, um Daten laden:

@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 Wenn MyViewModel als Abhängigkeit übergeben wird, ist MyDescendant weniger wiederverwendbar, da sind sie nun miteinander verbunden. Überlegen Sie, welche Alternative die Abhängigkeit in die Nachfolgerelemente und wendet die Umkehrung der Kontrollprinzipien an, macht den Ancestor für die Ausführung der Logik verantwortlich:

@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 er die untergeordnetes Element von seinen unmittelbaren Vorgängern. Zusammensetzbare Funktionen mit Ancestors zugunsten flexibler zusammensetzbarer Funktionen auf unterer Ebene.

@Composable Content-Lambdas können ebenfalls auf die gleiche Weise verwendet werden, Vorteile:

@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
    // ...
    ReusablePartOfTheScreen(
        content = {
            Button(
                onClick = {
                    myViewModel.loadData()
                }
            ) {
                Text("Confirm")
            }
        }
    )
}

@Composable
fun ReusablePartOfTheScreen(content: @Composable () -> Unit) {
    Column {
        // ...
        content()
    }
}