CompositionLocal
to narzędzie do przekazywania danych w składowej w sposób domyślny. Z tej strony dowiesz się więcej o tym, czym jest CompositionLocal
, jak utworzyć własną CompositionLocal
i czy CompositionLocal
to dobre rozwiązanie w Twoim przypadku.
Przedstawiamy CompositionLocal
Zwykle w funkcji Compose dane przepływają w dół Drzewo interfejsu jako parametry każdej funkcji kompozycyjnej. Dzięki temu zależności kompozytowe są jawne. Może to jednak być niewygodne w przypadku danych, które często i powszechnie stosowane, takie jak kolory czy styl pisania. Zobacz poniższe informacje przykład:
@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 ) }
Aby nie trzeba było przekazywać kolorów jako jawnej zależności parametru do większości komponentów, Compose udostępnia parametr CompositionLocal
, który umożliwia tworzenie obiektów nazwanych w zakresie drzewa, które można wykorzystać jako domyślny sposób przepływu danych przez drzewo interfejsu użytkownika.
CompositionLocal
elementy są zwykle dostarczane z wartością w określonym węźle drzewa interfejsu. Ta wartość może być używana przez potomków funkcji składanej bez deklarowania CompositionLocal
jako parametru w funkcji składanej.
CompositionLocal
to to, czego używa motyw Material.
MaterialTheme
to
obiekt, który zawiera 3 instancje CompositionLocal
: colorScheme
,
typography
i shapes
, dzięki czemu możesz je później pobrać w dowolnym elemencie podrzędnym.
są częścią kompozycji.
Dotyczy to w szczególności właściwości LocalColorScheme
, LocalShapes
i LocalTypography
, do których można uzyskać dostęp za pomocą atrybutów MaterialTheme
, colorScheme
, shapes
i typography
.
@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 ) }
Występ CompositionLocal
jest ograniczony do części kompozycji, dzięki czemu możesz podawać różne wartości na różnych poziomach drzewa. Wartość current
CompositionLocal
odpowiada najbliższej wartości podanej przez
elementu nadrzędnego w danej części kompozycji.
Aby podać nową wartość dla CompositionLocal
, użyj funkcji CompositionLocalProvider
i jej funkcji w nawiasach provides
, która łączy klucz CompositionLocal
z value
. Lambda content
obiektu CompositionLocalProvider
otrzyma podawaną wartość podczas uzyskiwania dostępu do właściwości current
obiektu CompositionLocal
. Gdy podasz nową wartość, Compose ponownie skompiluje części kompozycji, które odczytują CompositionLocal
.
Na przykład pole LocalContentColor
CompositionLocal
zawiera preferowany kolor treści używany w przypadku tekstu i
ikony, które kontrastują z bieżącym kolorem tła. W tym przykładzie zmienna CompositionLocalProvider
służy do podawania różnych wartości w różnych częściach kompozycji.
@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") }
Rysunek 1. Podgląd komponentu CompositionLocalExample
.
W ostatnim przykładzie instancje CompositionLocal
były używane wewnętrznie
w postaci plików kompozycyjnych Material. Aby uzyskać dostęp do bieżącej wartości elementu CompositionLocal
, użyj właściwości current
. W poniższym przykładzie bieżąca wartość Context
parametru LocalContext
Język CompositionLocal
, który jest często używany w aplikacjach na Androida, jest używany do formatowania
tekst:
@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) }
Tworzenie własnych CompositionLocal
CompositionLocal
to narzędzia do przekazywania danych w ramach kompozycji w sposób domyślny.
Innym ważnym sygnałem dotyczącym używania CompositionLocal
jest sytuacja, w której parametr ma postać
a także krzyżowe i pośrednie warstwy implementacji
istnieje, ponieważ informowanie tych warstw pośrednich ograniczyłoby
z rodzaju kompozycyjnego. Na przykład wysyłanie zapytań o uprawnienia Androida to
który zapewnia CompositionLocal
. Komponent selektora multimediów może dodawać nowe funkcje, aby uzyskać dostęp do treści chronionych hasłem na urządzeniu bez zmiany interfejsu API i wymagania od wywołujących selektor multimediów znajomości tego dodanego kontekstu używanego w środowisku.
Jednak CompositionLocal
nie zawsze jest najlepszym rozwiązaniem. Śr
Zniechęcaj do nadużywania funkcji CompositionLocal
, ponieważ ma ona pewne wady:
CompositionLocal
utrudnia analizowanie zachowania komponentu. Ponieważ wywołania takich komponentów tworzą ukryte zależności, wywołujący komponenty, które ich używają, muszą się upewnić, że wartość dla każdego CompositionLocal
jest spełniona.
Ponadto może nie być wyraźnego źródła informacji o tej zależności, ponieważ może ona ulec zmianie w dowolnej części Kompozycji. Dlatego debugowanie aplikacji przy
w których występują problemy, ponieważ trzeba zapoznać się z
Kompozycja, aby sprawdzić, gdzie podano wartość current
. Narzędzia takie jak Znajdź użycie w IDE lub Inspektor układu w Compose zawierają wystarczającą ilość informacji, aby zbadać ten problem.
Decydowanie o użyciu CompositionLocal
W pewnych przypadkach CompositionLocal
może być dobrym rozwiązaniem:
Parametr CompositionLocal
powinien mieć odpowiednią wartość domyślną. Jeśli nie ma wartości domyślnej, musisz zagwarantować, że bardzo trudno będzie deweloperowi znaleźć się w sytuacji, w której nie podano wartości parametru CompositionLocal
.
Brak wartości domyślnej może powodować problemy i frustrację podczas tworzenia testów lub podglądu komponentu, który używa tej funkcji. CompositionLocal
będzie zawsze wymagać podania tej wartości.
Unikaj tekstu CompositionLocal
w przypadku pojęć, które nie są ograniczone do drzewa lub
w hierarchii podrzędnej. Atrybut CompositionLocal
ma sens, kiedy może być
mogą być wykorzystywane przez wszystkie elementy podrzędne, a nie tylko niektóre z nich.
Jeśli Twoje zastosowanie nie spełnia tych wymagań, przed utworzeniem CompositionLocal
zapoznaj się z sekcją Możliwe rozwiązania.
Przykładem nieodpowiedniej metody jest tworzenie CompositionLocal
, które zawiera ViewModel
danego ekranu, aby wszystkie komponenty na tym ekranie mogły uzyskać odwołanie do ViewModel
w celu wykonania pewnych operacji. To zła praktyka
bo nie wszystkie elementy kompozycyjne znajdujące się poniżej danego drzewa UI muszą wiedzieć o
ViewModel
Dobrą praktyką jest przekazywanie komponentom tylko tych informacji, których potrzebują, zgodnie ze schematem, w którym stan przepływa w dół, a zdarzenia w górę. Dzięki temu komponenty będą bardziej przydatne i łatwiej je będzie testować.
Tworzę CompositionLocal
Do tworzenia CompositionLocal
można użyć 2 interfejsów API:
compositionLocalOf
: Zmiana wartości podanej podczas zmiany kompozycji powoduje unieważnienie tylko tego, treść, która czyta sięcurrent
.staticCompositionLocalOf
: W przeciwieństwie docompositionLocalOf
odczyty elementustaticCompositionLocalOf
nie są śledzić przez funkcję tworzenia wiadomości. Zmiana wartości powoduje ponowne złożenie całej funkcji lambdacontent
, w której podana jest wartośćCompositionLocal
, a nie tylko miejsc, w których wartośćcurrent
jest odczytywana w kompozycji.
Jeśli wartość podana w polu CompositionLocal
jest mało prawdopodobna do zmiany lub nigdy się nie zmieni, użyj pola staticCompositionLocalOf
, aby uzyskać korzyści związane ze skutecznością.
Na przykład system projektowania aplikacji może być subiektywny w sposobie, w jaki komponenty składane są wyróżniane za pomocą cienia dla komponentu interfejsu użytkownika. Ponieważ różne poziomy aplikacji powinny być propagowane w drzewie interfejsu, używamy elementu CompositionLocal
. Ponieważ wartość CompositionLocal
jest wyprowadzana warunkowo na podstawie motywu systemowego, używamy interfejsu API compositionLocalOf
:
// 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() }
Podawanie wartości w obszarze CompositionLocal
Komponent CompositionLocalProvider
przypisuje wartości do instancji CompositionLocal
w danej hierarchii. Aby podać nową wartość w polu CompositionLocal
, użyj funkcji
provides
infix, która wiąże klucz CompositionLocal
z value
w ten sposób:
// 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 } } } }
Wykorzystanie: CompositionLocal
CompositionLocal.current
zwraca wartość podaną przez najbliższy punkt CompositionLocalProvider
, który podaje wartość argumentowi CompositionLocal
:
@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 } }
Możliwe rozwiązania
W niektórych przypadkach zastosowanie parametru CompositionLocal
może być nadmiarowe. Jeśli
nie spełnia kryteriów określonych w Podejmowaniu decyzji, czy
CompositionLocal, inne rozwiązanie może być lepsze.
do Twoich potrzeb.
Przekazywanie parametrów jawnych
Wyraźne informowanie o zależnościach funkcji kompozycyjnej to dobry nawyk. Zalecamy, aby przekazywać komponentom tylko to, czego potrzebują. Aby zachęcić do rozdzielania i ponownego używania komponentów, każdy z nich powinien zawierać jak najmniej informacji.
@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 }
Odwrócenie kontroli
Innym sposobem na unikanie przekazywania niepotrzebnych zależności do komponentu jest inwersja kontroli. Zamiast tego, że element podrzędny przyjmuje zależność wykonaj jakąś logikę, a element nadrzędny.
Zapoznaj się z przykładem poniżej, w którym element podrzędny musi wyzwalać żądanie wczytaj trochę danych:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
W zależności od przypadku na firmie MyDescendant
spoczywa duża odpowiedzialność. Ponadto przekazanie MyViewModel
jako zależności powoduje, że MyDescendant
jest mniej użyteczny, ponieważ te elementy są teraz ze sobą połączone. Rozważ rozwiązanie, które nie przekazuje zależności potomkowi i korzysta z inwersji zasad kontroli, dzięki której rodzic jest odpowiedzialny za wykonywanie logiki:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
Takie podejście może się sprawdzić w niektórych przypadkach użycia, ponieważ rozdziela dziecko z jego bezpośrednich przodków. Składniki nadrzędne stają się coraz bardziej złożone, ponieważ mają bardziej elastyczne składniki niższego poziomu.
Podobnie lambda lambda dotycząca treści (@Composable
) może być używana w taki sam sposób,
te same korzyści:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Składnia motywu w sekcji Tworzenie
- Korzystanie z widoków w tworzeniu wiadomości
- Kotlin w Jetpack Compose