CompositionLocal, verileri dolaylı olarak oluşturma üzerinden iletmek için kullanılan bir araçtır. Bu sayfada, CompositionLocal hakkında daha ayrıntılı bilgi edinecek, kendi CompositionLocal çözümünüzü nasıl oluşturacağınızı öğrenecek ve CompositionLocal çözümünün kullanım alanınız için iyi bir çözüm olup olmadığını anlayacaksınız.
CompositionLocal ile tanışın
Compose'da genellikle veri akışı, her bir composable işleve parametre olarak kullanıcı arayüzü ağacından aşağı doğru gerçekleşir. Bu sayede, bir composable'ın bağımlılıkları açıkça belirtilir. Ancak bu, renkler veya yazı stilleri 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 ) }
Renklerin çoğu composable'a açık bir parametre bağımlılığı olarak iletilmesi ihtiyacını ortadan kaldırmak için Compose, CompositionLocal özelliğini sunar. Bu özellik, verilerin kullanıcı arayüzü ağacında dolaylı olarak akmasını sağlamak için kullanılabilecek ağaç kapsamlı adlandırılmış nesneler oluşturmanıza olanak tanır.
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şlevde parametre olarak tanımlamadan composable alt öğeleri tarafından kullanılabilir.
Önemli terimler: Bu kılavuzda oluşturma, kullanıcı arayüzü ağacı ve kullanıcı arayüzü hiyerarşisi terimleri kullanılmaktadır. Diğer kılavuzlarda birbirlerinin yerine kullanılsalar da farklı anlamlara sahiptirler:
- Oluşturma, composable işlevlerin çağrı grafiğinin kaydıdır.
- Kullanıcı arayüzü ağacı veya kullanıcı arayüzü hiyerarşisi, oluşturma işlemi tarafından oluşturulan, güncellenen ve sürdürülen
LayoutNodeağacıdır.
CompositionLocal, Material temasının arka planda kullandığı temadır.
MaterialTheme, üç CompositionLocal örneği sağlayan bir nesnedir: colorScheme, typography ve shapes. Bu örnekleri daha sonra oluşturmanın herhangi bir alt bölümünde almanıza olanak tanır.
Bunlar, MaterialTheme, colorScheme, shapes ve typography özellikleri aracılığıyla erişebileceğiniz LocalColorScheme, LocalShapes ve LocalTypography özellikleridir.
@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, oluşturmanın bir bölümüyle sınırlıdır. Bu sayede, ağacın farklı seviyelerinde farklı değerler sağlayabilirsiniz. Bir CompositionLocal öğesinin current değeri, oluşturmanın o bölümündeki bir üst öğe tarafından sağlanan en yakın değere karşılık gelir.
Bir CompositionLocal için yeni değer sağlamak üzere CompositionLocalProvider ve CompositionLocal anahtarını value ile ilişkilendiren provides infix işlevini kullanın. CompositionLocalProvider öğesinin content lambda'sı, CompositionLocal öğesinin current özelliğine erişirken sağlanan değeri alır. Yeni bir değer sağlandığında Compose, oluşturmanın CompositionLocal değerini okuyan kısımlarını yeniden oluşturur.
Örneğin, LocalContentColor CompositionLocal, mevcut arka plan rengiyle kontrast oluşturması için metin ve simgelerde kullanılan tercih edilen içerik rengini içerir. Aşağıdaki örnekte, oluşturmanın farklı bölümleri için farklı değerler sağlamak amacıyla CompositionLocalProvider kullanılmıştı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") }
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, Android uygulamalarında yaygın olarak kullanılan LocalContext CompositionLocal öğesinin mevcut Context değeri, metni biçimlendirmek için kullanılmıştı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 özelliğinizi oluşturun
CompositionLocal, verileri dolaylı olarak oluşturma işlemi üzerinden aktarmak için kullanılan bir araçtır.
CompositionLocal kullanmanın bir diğer önemli nedeni de parametrenin kesişen olması ve uygulamanın ara katmanlarının bu parametrenin varlığından haberdar olmaması gerektiğidir. Çünkü bu ara katmanların haberdar olması, composable'ın kullanımını sınırlar. Örneğin, Android izinlerini sorgulamak için arka planda bir CompositionLocal kullanılır. Medya seçme aracı composable'ı, API'sini değiştirmeden ve medya seçme aracını çağıranların ortamdan 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. Bazı dezavantajları olduğu için CompositionLocal özelliğinin aşırı kullanılmasını önermiyoruz:
CompositionLocal, bir composable'ın davranışını anlamayı zorlaştırır. Dolaylı bağımlılıklar oluşturdukları için bunları kullanan composable'ları çağıranların her CompositionLocal için bir değerin karşılandığından emin olması gerekir.
Ayrıca, oluşturmanın herhangi bir bölümünde değişebileceği için bu bağımlılığın net bir doğruluk kaynağı olmayabilir. Bu nedenle, bir sorun oluştuğunda uygulamada hata ayıklamak daha zor olabilir. current değerinin nerede sağlandığını görmek için oluşturma ağacında yukarı doğru gezinmeniz gerekir. IDE'deki Kullanımları bul veya Compose Layout Inspector gibi araçlar, bu sorunu azaltmak için yeterli bilgi sağlar.
CompositionLocal kullanıp kullanmayacağınıza karar verme
CompositionLocal, kullanım alanınız için iyi bir çözüm olabilir. Bunun için belirli koşullar vardır:
CompositionLocal için iyi bir varsayılan değer belirlenmelidir. Varsayılan değer yoksa geliştiricinin CompositionLocal için değer sağlanmayan bir duruma girmesinin son derece zor olmasını sağlamanız gerekir.
Varsayılan değer sağlanmaması, test oluştururken veya bu CompositionLocal öğesini kullanan bir composable'ı önizlerken sorunlara ve hayal kırıklığına neden olabilir. Bu durumda, değerin her zaman açıkça sağlanması gerekir.
Ağaç kapsamlı veya CompositionLocalalt hiyerarşi kapsamlı olarak düşünülmeyen kavramlar için kullanmaktan kaçının. CompositionLocal, birkaç alt öğe tarafından değil, potansiyel olarak herhangi bir alt öğe tarafından kullanılabildiğinde mantıklıdır.
Kullanım alanınız bu şartları karşılamıyorsa CompositionLocal oluşturmadan önce Değerlendirilebilecek alternatifler bölümüne göz atın.
Kötü bir uygulama örneği, belirli bir ekranın ViewModel değerini tutan bir CompositionLocal oluşturmaktır. Böylece, bu ekrandaki tüm composable'lar, bazı mantıkları gerçekleştirmek için ViewModel referansını alabilir. Bu kötü bir uygulamadır çünkü belirli bir kullanıcı arayüzü ağacının altındaki tüm composable'ların ViewModel hakkında bilgi sahibi olması gerekmez. En iyi uygulama, composable'lara yalnızca ihtiyaç duydukları bilgileri iletmektir. Bu bilgiler, durumun aşağı, etkinliklerin yukarı aktığı kalıbı takip eder.
Bu yaklaşım, composable'larınızı daha yeniden kullanılabilir ve test edilmesi daha kolay hale getirir.
CompositionLocal oluşturun
CompositionLocal oluşturmak için iki API vardır:
compositionLocalOf: Yeniden oluşturma sırasında sağlanan değerin değiştirilmesi,currentdeğerini okuyan içeriği yalnızca geçersiz kılar.staticCompositionLocalOf:compositionLocalOf'nin aksine,staticCompositionLocalOfokumaları Compose tarafından izlenmez. Değerin değiştirilmesi,CompositionLocaldeğerinin sağlandığıcontentlambda'sının tamamının yeniden oluşturulmasına neden olur. Bu durumda, yalnızca Oluşturma'dacurrentdeğerinin okunduğu yerler yeniden oluşturulmaz.
CompositionLocal için sağlanan değerin değişme olasılığı çok 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 kullanarak composable'ların yükseltilmesi konusunda katı olabilir. Uygulamanın farklı yükseklikleri kullanıcı arayüzü ağacına yayılacağından CompositionLocal kullanırız. CompositionLocal değeri, sistem temasına göre koşullu olarak türetildiğinden 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ğerler girin
CompositionLocalProvider composable, değerleri belirli hiyerarşi için CompositionLocal örneklere bağlar. Bir CompositionLocal için yeni değer sağlamak üzere, CompositionLocal anahtarını value ile ilişkilendiren provides infix işlevini aşağıdaki gibi 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 kullanma
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ğerlendirilebilecek alternatifler
CompositionLocal, bazı kullanım alanları için aşırı bir çözüm olabilir. Kullanım alanınız CompositionLocal kullanılıp kullanılmayacağına karar verme bölümünde belirtilen ölçütleri karşılamıyorsa kullanım alanınıza başka bir çözüm daha uygun olabilir.
Açık parametreleri iletme
Oluşturulabilir öğenin bağımlılıkları konusunda açık olmak iyi bir alışkanlıktır. Oluşturulabilir öğelere yalnızca ihtiyaç duydukları bilgileri iletmenizi öneririz. Birleştirilebilir öğelerin ayrıştırılmasını ve yeniden kullanılmasını teşvik etmek için her birleştirilebilir öğe mümkün olduğunca az bilgi 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 composable'a gereksiz bağımlılıklar aktarmaktan kaçınmanın başka bir yolu da denetimin tersine çevrilmesi yöntemini kullanmaktır. Alt öğenin bir mantığı yürütmek için bağımlılık alması yerine, üst öğe bunu yapar.
Bir alt öğenin bazı verileri yüklemek için isteği 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 çok fazla sorumluluk üstlenebilir. Ayrıca, MyViewModel'i bağımlılık olarak iletmek, MyDescendant'in yeniden kullanılabilirliğini azaltır çünkü artık birbirlerine bağlıdırlar. Bağımlılığı alt öğeye aktarmayan ve mantığı yürütmekten üst öğeyi sorumlu kılan kontrolün tersine çevrilmesi 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ığı için bazı kullanım alanlarına daha uygun olabilir. Üst düzey composable'lar, daha esnek alt düzey composable'lar sağlamak için daha karmaşık hale gelir.
Benzer şekilde, @Composable içerik lambda'ları da aynı şekilde kullanılarak aynı avantajlar elde edilebilir:
@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.
- Compose'da temanın anatomisi
- Compose'da görünümleri kullanma
- Jetpack Compose için Kotlin