CompositionLocal, verileri Kompozisyon aracılığıyla örtülü olarak iletmek için kullanılan bir araçtır. Bu sayfada, CompositionLocal hakkında daha ayrıntılı bilgi edinecek, kendi CompositionLocal öğenizi nasıl oluşturacağınızı öğrenecek ve CompositionLocal öğesinin kullanım alanınız için iyi bir çözüm olup olmadığını anlayacaksınız.
CompositionLocal ile tanışın
Genellikle Compose'da veriler, kullanıcı arayüzü ağacında aşağı doğru akar ve her composable işlev için parametre olarak kullanılır. Bu, composable'ın bağımlılıklarını açık hale getirir. Ancak bu durum, renkler veya yazı tipi 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 parametre bağımlılığı olarak iletilmesine gerek olmaması için Compose, CompositionLocal sunar. Bu, verilerin kullanıcı arayüzü ağında akmasını sağlamak için örtülü bir yol olarak kullanılabilecek 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 birlikte sağlanır. Bu değer, CompositionLocal parametresi composable işlevinde bildirilmeden composable öğenin alt öğeleri tarafından kullanılabilir.
CompositionLocal, Material temasının arka planda kullandığı şeydir.
MaterialTheme, üç CompositionLocal örneği (colorScheme, typography ve shapes) sağlayan bir nesnedir. Bu örnekleri daha sonra Kompozisyon'un herhangi bir alt bölümünde alabilirsiniz.
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, Kompozisyonun bir bölümüyle sınırlıdır. Böylece, ağacın farklı seviyelerinde farklı değerler sağlayabilirsiniz. Bir CompositionLocal öğesinin current değeri, Kompozisyonun 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 bir değer sağlamak üzere
CompositionLocalProvider
ve provides
bir CompositionLocal anahtarını bir value ile ilişkilendiren 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, CompositionLocal okuyan Kompozisyon'un bölümlerini yeniden oluşturur.
Buna örnek olarak, LocalContentColor CompositionLocal, metin ve ikonografide kullanılan tercih edilen içerik rengini içerir. Bu renk, mevcut arka plan rengiyle kontrast oluşturur. Aşağıdaki örnekte, CompositionLocalProvider, kompozisyonun farklı bölümleri için farklı değerler sağlamak üzere 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") }
CompositionLocalExample composable'ının önizlemesi.Son örnekte, CompositionLocal örnekleri Material composable'lar tarafından dahili olarak kullanıldı. Bir CompositionLocal öğesinin geçerli 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ılıyor:
@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 içeriğinizi oluşturma CompositionLocal
CompositionLocal, verileri Composition üzerinden örtülü olarak aktarmak için kullanılan bir araçtır.
CompositionLocal kullanmanın bir diğer önemli sinyali, parametrenin kesişen olduğu ve uygulama ara katmanlarının bunun varlığından haberdar olmaması gerektiği durumlardır. Çünkü bu ara katmanların haberdar olması, birleştirilebilir öğenin faydasını sınırlar. Örneğin, Android izinleri için sorgu oluşturma, arka planda CompositionLocal tarafından sağlanır. Medya seçici composable'ı, API'sini değiştirmeden ve medya seçiciyi çağıranların ortamdan kullanılan bu ek bağlamın farkında 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'ın bazı dezavantajları olduğundan aşırı kullanılmasını önermiyoruz:
CompositionLocal, composable'ın davranışını anlamayı zorlaştırır. Bunlar örtülü bağımlılıklar oluşturduğundan, bunları kullanan composable'ların arayanları, her CompositionLocal için bir değerin karşılandığından emin olmalıdır.
Ayrıca, Kompozisyonun herhangi bir bölümünde değişebileceğinden bu bağımlılık için net bir doğruluk kaynağı olmayabilir. Bu nedenle, current değerinin nerede sağlandığını görmek için Composition'da yukarı gitmeniz gerektiğinden bir sorun oluştuğunda uygulamada hata ayıklamak daha zor olabilir. IDE'deki Kullanım yerlerini bul veya Compose düzen inceleyici gibi araçlar, bu sorunu azaltmak için yeterli bilgiyi sağlar.
CompositionLocal kullanıp kullanmayacağınıza karar verme
Kullanım alanınız için CompositionLocal'ı iyi bir çözüm haline getirebilecek belirli koşullar vardır:
CompositionLocal iyi bir varsayılan değere sahip olmalıdır. Varsayılan değer yoksa geliştiricinin CompositionLocal için değer sağlanmadığı bir duruma girmesinin son derece zor olacağını garanti etmeniz gerekir.
Varsayılan değer sağlanmaması, test oluştururken veya bu CompositionLocal'yı kullanan bir composable'ı önizlerken sorunlara ve hayal kırıklığına neden olabilir. Bu durumda, CompositionLocal'nın her zaman açıkça sağlanması gerekir.
Ağaç kapsamlı veya alt hiyerarşi kapsamlı olarak düşünülmeyen kavramlar için CompositionLocal kullanmayın. CompositionLocal, birkaç torun tarafından değil, herhangi bir torun tarafından kullanılma ihtimali olduğunda 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 CompositionLocal değerini tutan bir ViewModel oluşturmaktır. Böylece, bu ekrandaki tüm composable'lar, bazı mantık işlemlerini gerçekleştirmek için ViewModel değerine referans 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. İyi uygulama, durum aşağı akar ve etkinlikler yukarı akar kalıbını izleyerek yalnızca composable'ların ihtiyaç duyduğu bilgileri iletmektir. Bu yaklaşım, composable işlevlerinizi daha fazla yeniden kullanılabilir ve test etmesi daha kolay hale getirir.
CompositionLocal oluşturma
CompositionLocal oluşturmak için iki API vardır:
compositionLocalOf: Yeniden oluşturma sırasında sağlanan değerin değiştirilmesi, yalnızcacurrentdeğerini okuyan içeriği geçersiz kılar.staticCompositionLocalOf:compositionLocalOf'den farklı olarak,staticCompositionLocalOf'nin okunma sayısı Compose tarafından izlenmez. Değerin değiştirilmesi,CompositionLocaldeğerinin sağlandığıcontentlambda'nın tamamının, yalnızcacurrentdeğerinin Composition'da okunduğu yerler yerine yeniden oluşturulmasına neden olur.
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 sisteminde, kullanıcı arayüzü bileşeni için gölge kullanılarak composable'ların yükseltilme şekliyle ilgili belirli tercihler olabilir. Uygulamanın farklı yükseklikleri kullanıcı arayüzü ağacına yayılması gerektiğinden CompositionLocal kullanıyoruz. CompositionLocal değeri, sistem temasına bağlı olarak 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 sağlama
CompositionLocalProvider
Composable'ı, belirli bir hiyerarşi için değerleri CompositionLocal örneklerine bağlar. CompositionLocal öğesine yeni bir değer sağlamak için provides infix işlevini kullanarak CompositionLocal anahtarını value ile aşağıdaki gibi ilişkilendirin:
// 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üketimi
CompositionLocal.current, CompositionLocal öğesine 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, Deciding whether to use
CompositionLocal (CompositionLocal'ı kullanıp kullanmayacağınıza karar verme) bölümünde belirtilen ölçütleri karşılamıyorsa kullanım alanınız için başka bir çözüm daha uygun olabilir.
Açık parametreleri iletme
Composable'ın bağımlılıkları hakkında açık olmak iyi bir alışkanlıktır. Composable işlevlere yalnızca ihtiyaç duydukları parametreleri iletmenizi öneririz. Birleştirilebilir işlevlerin ayrılmasını ve yeniden kullanılmasını teşvik etmek için her birleştirilebilir işlev 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 aktarmayı önlemenin bir diğer yolu da kontrolün tersine çevrilmesini kullanmaktır. Alt öğe, bazı mantıkları yürütmek için bağımlılık almak yerine bunu üst öğe 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 çok fazla sorumluluk sahibi olabilir. Ayrıca, MyViewModel öğesini bağımlılık olarak iletmek, MyDescendant öğesini daha az yeniden kullanılabilir hale getirir. Çünkü artık birlikte kullanılırlar. Bağımlılığı alt öğeye geçirmeyen ve kontrolü tersine çevirme ilkelerini kullanarak mantığı yürütmekten üst öğeyi sorumlu kılan alternatifi göz önünde bulundurun:
@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 öğe composable'lar, daha esnek alt düzey composable'lar kullanmak için daha karmaşık hale gelir.
Benzer şekilde, @Composable içerik lambdaları da aynı avantajları elde etmek 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.
- Compose'daki temanın anatomisi
- Oluşturma penceresinde görünümleri kullanma
- Jetpack Compose için Kotlin