CompositionLocal
to narzędzie do domyślnego przekazywania danych przez kompozycję. Na tej stronie znajdziesz bardziej szczegółowe informacje o tym, czym jest CompositionLocal
, jak utworzyć własny projekt CompositionLocal
. Dowiesz się też, czy CompositionLocal
sprawdzi się w Twoim przypadku.
Przedstawiamy CompositionLocal
Zwykle w interfejsie Compose dane są przesyłane przez drzewo interfejsu jako parametry do każdej funkcji kompozycyjnej. To pozwala uwydatnić zależności kompozycyjnego. Może to jednak być kłopotliwe w przypadku danych, które są bardzo często i powszechnie używane, takich jak kolory czy style czcionki. Zobacz ten 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 zapobiec konieczności przekazywania kolorów jako jawnej zależności parametrów do większości funkcji kompozycyjnych, funkcja Tworzenie funkcji CompositionLocal
umożliwia tworzenie nazwanych obiektów o zakresie na poziomie drzewa, które można wykorzystać jako niejawny sposób przepływu danych przez drzewo interfejsu.
Elementy CompositionLocal
mają zwykle przypisaną wartość w określonym węźle drzewa interfejsu. Tej wartości można używać przez jej elementy podrzędne z możliwością kompozycji bez deklarowania CompositionLocal
jako parametru funkcji kompozycyjnej.
CompositionLocal
to funkcja wykorzystywana w trybie Material Design.
MaterialTheme
to obiekt, który zawiera 3 wystąpienia CompositionLocal
(kolory, typografia i kształty) – co umożliwia ich późniejsze pobranie w dowolnej podrzędnej części kompozycji. Są to właściwości LocalColors
, LocalShapes
i LocalTypography
, do których możesz uzyskać dostęp za pomocą atrybutów MaterialTheme
colors
, shapes
i 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 ) }
Instancja CompositionLocal
jest ograniczona do części kompozycji, więc możesz podać różne wartości na różnych poziomach drzewa. Wartość current
parametru CompositionLocal
odpowiada najbliższej wartości podanej przez element nadrzędny w tej części kompozycji.
Aby podać nową wartość w obiekcie CompositionLocal
, użyj funkcji CompositionLocalProvider
i jej funkcjiinfikacji provides
, która wiąże klucz CompositionLocal
z kluczem value
. Lambda content
obiektu CompositionLocalProvider
otrzyma podaną wartość podczas uzyskiwania dostępu do właściwości current
obiektu CompositionLocal
. Gdy podasz nową wartość, funkcja Utwórz ponownie utworzy fragmenty kompozycji, które odczytują element CompositionLocal
.
Na przykład w polu LocalContentAlpha
CompositionLocal
znajduje się preferowana treść w wersji alfa używana w przypadku tekstu i ikony, aby uwydatnić lub złagodzić różne elementy interfejsu. W poniższym przykładzie użyto funkcji CompositionLocalProvider
do określenia różnych wartości dla różnych części kompozycji.
@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") }
Rysunek 1. Podgląd funkcji kompozycyjnej CompositionLocalExample
.
We wszystkich powyższych przykładach instancje CompositionLocal
były wewnętrznie używane przez elementy kompozycyjne Material. Aby poznać bieżącą wartość właściwości CompositionLocal
, użyj jej właściwości current
. W poniższym przykładzie do formatowania tekstu używana jest bieżąca wartość Context
elementu LocalContext
CompositionLocal
, która jest często używana w aplikacjach na Androida:
@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) }
Tworzę Twój własny CompositionLocal
.
CompositionLocal
to narzędzie do domyślnego przekazywania danych przez kompozycję.
Kolejnym kluczowym sygnałem dotyczącym użycia CompositionLocal
jest sytuacja, w której parametr ma charakter pośredni, a pośrednie warstwy implementacji nie powinny wiedzieć o istnieniu tego parametru, ponieważ uświadomienie w tych warstwach pośrednich ograniczenia przydatności funkcji kompozycyjnej. Na przykład wysyłanie zapytań o uprawnienia Androida jest przeprowadzane przy użyciu funkcji CompositionLocal
. Element kompozycyjny selektora mediów może dodać nową funkcję, która zapewnia dostęp do treści chronionych uprawnieniami na urządzeniu bez zmiany interfejsu API i wymaga, aby elementy wywołujące selektor mediów miały świadomość tego dodatkowego kontekstu używanego ze środowiska.
Jednak CompositionLocal
nie zawsze jest najlepszym rozwiązaniem. Odradzamy nadużywanie usługi CompositionLocal
, ponieważ ma ona pewne wady:
CompositionLocal
utrudnia racjonalne działanie funkcji kompozycyjnej. Tworząc pośrednie zależności, elementy wywołujące funkcje kompozycyjne, które z nich korzystają, muszą dbać o to, aby spełniony jest warunek każdego elementu CompositionLocal
.
Poza tym zależność ta może zmieniać się w dowolnej części kompozycji, nie może więc nie być jasnego źródła informacji. Dlatego debugowanie aplikacji w przypadku wystąpienia problemu może być trudniejsze, ponieważ trzeba przejść do bardziej szczegółowego widoku kompozycji, aby sprawdzić, gdzie wpisano wartość current
. Narzędzia takie jak wyszukiwanie użycia w IDE lub inspektor układu tworzenia wiadomości dostarczają wystarczającą ilość informacji do ograniczenia tego problemu.
Decydowanie, czy użyć aplikacji CompositionLocal
Oto pewne warunki, które sprawiają, że usługa CompositionLocal
może być dobrym rozwiązaniem w Twoim przypadku:
CompositionLocal
powinien mieć dobrą wartość domyślną. W przypadku braku wartości domyślnej musisz zagwarantować, że w sytuacji, w której deweloper nie ma podanej wartości CompositionLocal
, może wyjątkowo trudno dotrzeć do celu.
Brak wartości domyślnej może powodować problemy i frustrację podczas tworzenia testów lub wyświetlania podglądu funkcji kompozycyjnej, która korzysta z elementu CompositionLocal
, który zawsze wymaga jego jawnego podania.
Unikaj wartości CompositionLocal
w przypadku koncepcji, które nie są uważane za ograniczone do drzewa lub podhierarchii. Parametr CompositionLocal
ma sens, jeśli może być potencjalnie używany przez dowolne elementy podrzędne, a nie kilka z nich.
Jeśli Twój przypadek użycia nie spełnia tych wymagań, przed utworzeniem CompositionLocal
zapoznaj się z sekcją Alternatywy, które warto rozważyć.
Przykładem złej praktyki jest utworzenie elementu CompositionLocal
zawierającego ViewModel
konkretnego ekranu, tak aby wszystkie elementy kompozycyjne na tym ekranie mogły zawierać odwołanie do ViewModel
w celu wykonania pewnych działań logicznych. Jest to dobra praktyka, ponieważ nie wszystkie funkcje kompozycyjne znajdujące się pod danym drzewem interfejsu muszą wiedzieć o elemencie ViewModel
. Zalecaną praktyką jest przekazywanie do funkcji kompozycyjnych tylko tych informacji, których są niezbędne, zgodnie ze wzorcem przepływu stanu w dół, a zdarzeń w górę. Dzięki temu kompozycje będą bardziej przydatne
i będą łatwiejsze do testowania.
Tworzę CompositionLocal
CompositionLocal
można utworzyć za pomocą 2 interfejsów API:
compositionLocalOf
: zmiana wartości podanej podczas zmiany kompozycji spowoduje, że tylko treść, która odczytuje jej wartośćcurrent
, staje się nieprawidłowa.staticCompositionLocalOf
: W przeciwieństwie do elementucompositionLocalOf
odczyty elementustaticCompositionLocalOf
nie są śledzone przez funkcję tworzenia. Zmiana wartości powoduje ponowne utworzenie całej funkcjicontent
, w której określonoCompositionLocal
, a nie tylko miejsc, w których jest odczytywana wartośćcurrent
w kompozycji.
Jeśli wartość podana w polu CompositionLocal
jest mało prawdopodobne lub nigdy się nie zmieni, użyj funkcji staticCompositionLocalOf
, aby zwiększyć wydajność.
Na przykład system projektowania aplikacji może być oceniany w ten sposób, że elementy kompozycyjne są podnoszone za pomocą cienia komponentu UI. Różne wysokości aplikacji powinny być rozpowszechnione w całym drzewie interfejsu, więc używamy metody CompositionLocal
. Wartość CompositionLocal
jest generowana warunkowo na podstawie motywu systemu, więc 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() }
Podanie wartości w funkcji CompositionLocal
Funkcja kompozycyjna CompositionLocalProvider
wiąże wartości z CompositionLocal
instancji w danej hierarchii. Aby podać nową wartość w elemencie CompositionLocal
, użyj funkcjiinfikacji provides
, 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
Funkcja CompositionLocal.current
zwraca wartość podaną przez najbliższy argument CompositionLocalProvider
, który stanowi wartość tego parametru CompositionLocal
:
@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 } }
Inne rozwiązania
W niektórych przypadkach użycie funkcji CompositionLocal
może być zbyt długie. Jeśli Twój przypadek użycia nie spełnia kryteriów określonych w sekcji Decydowanie, czy użyć CompositionLocal, prawdopodobnie inne rozwiązanie może być lepiej dopasowane do Twojego przypadku.
Przekazuj parametry jawne
Wyraźne informowanie o zależnościach elementu kompozycyjnego to dobry nawyk. Zalecamy udostępnianie elementów kompozycyjnych tylko potrzebnych. Aby zachęcać do odseparowania i ponownego użycia elementów kompozycyjnych, każdy element kompozycyjny 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 uniknięcia przekazywania niepotrzebnych zależności do funkcji kompozycyjnej jest odwrócenie kontroli. Zamiast tego, aby element podrzędny działał logicznie, musi to robić.
Oto przykład, w którym element podrzędny musi aktywować żądanie wczytania 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 MyDescendant
może ponosić dużą odpowiedzialność. Poza tym brak zależności MyViewModel
sprawia, że MyDescendant
mniej czasu może zostać użyte ponownie, ponieważ są teraz połączone. Rozważmy alternatywę, która nie przekazuje zależności do elementu podrzędnego i korzysta z odwrócenia zasad kontroli, co sprawia, że element nadrzędny jest odpowiedzialny za wykonanie tej logiki:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
To podejście może się sprawdzić w niektórych przypadkach użycia, ponieważ odłącza element podrzędny od jego bezpośrednich elementów nadrzędnych. Elementy tego typu są zwykle bardziej złożone na rzecz bardziej elastycznych elementów kompozycyjnych niższego poziomu.
I podobnie, lambda funkcji @Composable
można używać w ten sam sposób, aby uzyskać 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 JavaScript jest wyłączony
- Anatomia motywu w sekcji Utwórz
- Korzystanie z widoków w sekcji Utwórz
- Kotlin dla Jetpack Compose