CompositionLocal
, verileri kompozisyon üzerinden dolaylı olarak iletmek için kullanılan bir araçtır. Bu sayfada, CompositionLocal
'nin ne olduğunu daha ayrıntılı bir şekilde öğrenecek, kendi CompositionLocal
'nizi nasıl oluşturacağınızı öğrenecek ve CompositionLocal
'nin kullanım alanınız için iyi bir çözüm olup olmadığını anlayacaksınız.
CompositionLocal
ile tanışın
Genellikle Compose'da, her bir birleştirilebilir işlev için parametre olarak veriler kullanıcı arayüzü ağacından aşağı akar. Bu, bir bileşenin bağımlılıklarını açık hale getirir. Ancak bu, renk veya yazı stili gibi çok sık ve yaygın olarak kullanılan veriler için zahmetli olabilir. Aşağıdaki örneğe bakın:
@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 ) }
Renkleri çoğu bileşime açık parametre bağımlılığı olarak iletme ihtiyacını ortadan kaldırmak için Compose, kullanıcı arayüzü ağacında veri akışı sağlamak için açık olmayan bir yöntem olarak kullanılabilecek ağaç kapsamlı adlandırılmış nesneler oluşturmanıza olanak tanıyan CompositionLocal
'yi sunar.
CompositionLocal
öğeleri genellikle kullanıcı arayüzü ağacının belirli bir düğümünde bir değerle sağlanır. Bu değer, CompositionLocal
öğesini composable işlevinde parametre olarak bildirmeden composable alt öğeleri tarafından kullanılabilir.
CompositionLocal
, Material temasının temelinde kullanılan öğedir.
MaterialTheme
, colorScheme
, typography
ve shapes
olmak üzere üç CompositionLocal
örneği sağlayan bir nesnedir. Bu örnekleri daha sonra bileşimin herhangi bir alt öğesinde alabilirsiniz.
MaterialTheme
colorScheme
, shapes
ve typography
özellikleri üzerinden erişebileceğiniz LocalColorScheme
, LocalShapes
ve LocalTypography
özellikleri şunlardır.
@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 ) }
CompositionLocal
örneği, kompozisyonun bir bölümüne göre kapsamlandırılır. Böylece ağacın farklı seviyelerinde farklı değerler sağlayabilirsiniz. Bir CompositionLocal
öğesinin current
değeri, kompozisyonun ilgili bölümünde bir ata tarafından sağlanan en yakın değere karşılık gelir.
CompositionLocal
öğesine yeni bir değer sağlamak için CompositionLocalProvider
ve CompositionLocal
anahtarını value
ile ilişkilendiren provides
düzeltme işlevini kullanın. CompositionLocalProvider
'un content
lambdası, CompositionLocal
'ın current
özelliğine erişirken sağlanan değeri alır. Yeni bir değer sağlandığında, Oluştur işlevi, CompositionLocal
değerini okuyan kompozisyonun bölümlerini yeniden oluşturur.
Buna örnek olarak, LocalContentColor
CompositionLocal
, metin ve simge için kullanılan tercih edilen içerik rengini içerir. Bu renk, mevcut arka plan rengiyle kontrast oluşturacak şekilde seçilir. Aşağıdaki örnekte, kompozisyonun farklı bölümleri için farklı değerler sağlamak üzere CompositionLocalProvider
kullanılır.
@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") }
Şekil 1. CompositionLocalExample
bileşeninin önizlemesi.
Son örnekte, CompositionLocal
örnekleri Material bileşenleri tarafından dahili olarak kullanılmıştır. Bir CompositionLocal
öğesinin mevcut değerine erişmek için current
özelliğini kullanın. Aşağıdaki örnekte, metni biçimlendirmek için Android uygulamalarında yaygın olarak kullanılan LocalContext
CompositionLocal
özelliğinin mevcut Context
değeri kullanılmaktadır:
@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) }
Kendi CompositionLocal
CompositionLocal
, kompozisyon aracılığıyla verileri dolaylı olarak aktarmak için kullanılan bir araçtır.
CompositionLocal
kullanmak için bir diğer önemli sinyal, parametrenin çapraz kesiştiği ve uygulamanın ara katmanlarının varlığından haberdar olmamasıdır. Çünkü bu ara katmanların farkında olması, composable'ın faydasını kısıtlayacaktır. Örneğin, Android izinleri için sorgu, temelde bir CompositionLocal
tarafından sağlanır. Bir medya seçici bileşeni, API'sini değiştirmeden ve medya seçiciyi çağıranların ortamda kullanılan bu ek bağlamdan haberdar olmasını gerektirmeden cihazdaki izin korumalı içeriğe erişmek için yeni işlevler ekleyebilir.
Ancak CompositionLocal
her zaman en iyi çözüm değildir. CompositionLocal
'nin bazı dezavantajları olduğundan aşırı kullanımından kaçınmanızı öneririz:
CompositionLocal
, bir bileşenin davranışının anlaşılmasını zorlaştırır. Örtülü bağımlılıklar oluşturdukları için, bunları kullanan birleştirilebilir öğelerin çağırıcılarının her CompositionLocal
için bir değer sağlandığından emin olması gerekir.
Ayrıca, bu bağımlılık, kompozisyonun herhangi bir yerinde mutasyona uğrayabileceğinden, bu bağımlılık için net bir doğruluk kaynağı olmayabilir. Dolayısıyla, current
değerinin nerede sağlandığını görmek için Beste'de yukarı gitmeniz gerekeceğinden, bir sorun oluştuğunda uygulamadaki hataları ayıklamak daha zor olabilir. IDE'deki Kullanım yerlerini bul veya Düzen oluşturma inceleyici gibi araçlar, bu sorunu azaltmak için yeterli bilgi sağlar.
CompositionLocal
kullanmaya karar verme
CompositionLocal
'ü kullanım alanınız için iyi bir çözüm haline getirebilecek belirli koşullar vardır:
CompositionLocal
'in iyi bir varsayılan değeri olmalıdır. Varsayılan değer yoksa geliştiricilerin CompositionLocal
için bir değer sağlamadığı bir duruma düşmelerinin son derece zor olduğunu garanti etmeniz gerekir.
Varsayılan değer sağlamamak, test oluştururken veya bu özelliği kullanan bir bileşeni önizlerken sorunlara ve can sıkıcı durumlara neden olabilir. CompositionLocal
her zaman açıkça sağlanmalıdır.
Ağaç kapsamlı veya alt hiyerarşi kapsamlı olarak düşünülmeyen kavramlar için CompositionLocal
kullanmayın. CompositionLocal
, birkaçı tarafından değil, tüm alt öğeler tarafından kullanılabilecek durumlarda anlamlıdır.
Kullanım alanınız bu şartları karşılamıyorsa CompositionLocal
oluşturmadan önce Dikkate alınacak alternatifler bölümüne göz atın.
Belirli bir ekranın ViewModel
öğesini içeren bir CompositionLocal
oluşturmak kötü uygulamaya örnek olarak verilebilir. Böylece söz konusu ekrandaki tüm composable'lar, bir mantık gerçekleştirmek için ViewModel
öğesine referans verebilir. Belirli bir kullanıcı arayüzü ağacının altındaki tüm bileşenlerin ViewModel
hakkında bilgi sahibi olması gerekmediğinden bu kötü bir uygulamadır. En iyi uygulama, durum aşağı, etkinlikler yukarı akışı modelini izleyerek yalnızca gerekli bilgileri birleştirilebilir öğelere iletmektir. Bu yaklaşım, composable'larınızı daha çok
kullanılabilir ve test edilmesini kolaylaştıracak.
CompositionLocal
oluşturma
CompositionLocal
oluşturmak için iki API vardır:
compositionLocalOf
: Yeniden derleme sırasında sağlanan değerin değiştirilmesi,current
yalnızca değerini okuyan içeriği geçersiz kılar.staticCompositionLocalOf
:compositionLocalOf
'ın aksine,staticCompositionLocalOf
'un okumaları Compose tarafından izlenmez. Değerin değiştirilmesi,current
değerinin Kompozisyon'da okunduğu yerlerin yerineCompositionLocal
değerinin sağlandığıcontent
lambda işlevinin tamamının yeniden derlenmesine neden olur.
CompositionLocal
için sağlanan değerin değişme olasılığı düşükse veya hiç değişmeyecekse performans avantajlarından yararlanmak için staticCompositionLocalOf
kullanın.
Örneğin, bir uygulamanın tasarım sistemi, kullanıcı arayüzü bileşeni için gölge kullanılarak bileşenlerin öne çıkarılma şekliyle ilgili fikir sahibi olabilir. Uygulamanın farklı yükseklikleri kullanıcı arayüzü ağacı boyunca yayılacağından bir CompositionLocal
kullanırız. CompositionLocal
değeri, sistem temasına göre koşullu olarak türetildiği için compositionLocalOf
API'sini kullanırız:
// 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() }
CompositionLocal
için değer sağlama
CompositionLocalProvider
bileşeni, değerleri belirli bir hiyerarşi için CompositionLocal
örneklerine bağlar. Bir CompositionLocal
öğesine yeni bir değer sağlamak için CompositionLocal
anahtarını aşağıdaki şekilde bir value
ile ilişkilendiren provides
infix işlevini kullanın:
// 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
'ü tüketme
CompositionLocal.current
, ilgili CompositionLocal
için değer sağlayan en yakın CompositionLocalProvider
tarafından sağlanan değeri döndürür:
@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 } }
Değerlendirebileceğiniz alternatifler
CompositionLocal
, bazı kullanım alanları için aşırı bir çözüm olabilir. Kullanım alanınız CompositionLocal'ı kullanıp kullanmayacağınıza karar verme bölümünde belirtilen ölçütleri karşılamıyorsa kullanım alanınıza daha uygun başka bir çözüm olabilir.
Açık parametreleri iletme
composable'ın bağımlılıklarını açıkça belirtmek iyi bir alışkanlıktır. Kompozitlere yalnızca ihtiyaç duydukları bilgileri iletmenizi öneririz. composable'ların ayrıştırılmasını ve tekrar kullanılmasını teşvik etmek için her composable, mümkün olan en az miktarda bilgiyi içermelidir.
@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 }
Kontrolün tersine çevrilmesi
Bir bileşene gereksiz bağımlılıkların aktarılmasını önlemenin bir diğer yolu da kontrolün tersine çevrilmesidir. Alt öğe bir mantık yürütmek için bağımlılığı almak yerine bunu yapar.
Bir alt öğenin bazı verileri yükleme isteğini tetiklemesi gereken aşağıdaki örneğe bakın:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
Duruma bağlı olarak MyDescendant
'ün çok fazla sorumluluğu olabilir. Ayrıca, MyViewModel
'ü bağımlılık olarak iletmek, artık birlikte bağlandıkları için MyDescendant
'ü daha az yeniden kullanılabilir hale getirir. Bağımlılığı alt öğeye aktarmayan ve mantığı yürütmekten üst öğeyi sorumlu tutan kontrol inversiyonu ilkelerini kullanan alternatifi düşünün:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
Bu yaklaşım, alt öğeyi doğrudan üst öğelerinden ayırdığından bazı kullanım alanları için daha uygun olabilir. Üst öğe derleyiciler, daha esnek alt düzey derleyiciler elde etmek için daha karmaşık hale gelir.
Benzer şekilde, @Composable
içerik lambda'ları da aynı avantajlardan yararlanmak için aynı şekilde kullanılabilir:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Oluştur'daki bir temanın anatomisi
- Oluşturma bölümünde görünümleri kullanma
- Jetpack Compose için Kotlin