CompositionLocal, verileri Composition üzerinden örtülü olarak aktarmak 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 her bir composable işlevine parametre olarak aşağı doğru akar. 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ığı teknolojidir.
MaterialTheme, üç CompositionLocal örneği sağlayan bir nesnedir: colorScheme, typography ve shapes. 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. Bu nedenle, 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. CompositionLocal öğesinin current özelliğine erişilirken CompositionLocalProvider öğesinin content lambda'sı 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, metni biçimlendirmek için Android uygulamalarında yaygın olarak kullanılan LocalContext CompositionLocal öğesinin 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 kitlenizi 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 işlemi, arka planda CompositionLocal tarafından sağlanır. Medya seçici composable'ı, API'sini değiştirmeden ve medya seçicinin arayanlarını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 aşırı kullanımının bazı dezavantajları olduğundan bu durum önerilmez:
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 bilgi 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 için iyi bir varsayılan değer olmalıdır. Varsayılan değer yoksa geliştiricinin CompositionLocal için değer sağlanmayan bir duruma girmesinin son derece zor olacağını garanti etmeniz gerekir.
Varsayılan değer sağlamamak, 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 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 potansiyel olarak kullanılabildiğinde mantıklıdır.
Kullanım alanınız bu şartları karşılamıyorsa CompositionLocal oluşturmadan önce Dikkate alınabilecek 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ğı, etkinlikler yukarı akar kalıbını izleyerek yalnızca composable'lara ihtiyaç duydukları bilgileri iletmektir. Bu yaklaşım, composable işlevlerinizi daha fazla yeniden kullanılabilir ve test etmeyi kolaylaştırır.
CompositionLocal oluştur
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,CompositionLocalöğesinin 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ükseltilmesi konusunda belirli bir görüş 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 şekilde elde edildiğ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 CompositionLocal anahtarını value ile ilişkilendiren provides infix işlevini aşağıdaki şekilde 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üketimi
CompositionLocal.current, 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, 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ü tersine çevirme 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 bir temanın anatomisi
- Compose'da Görünümleri Kullanma
- Jetpack Compose için Kotlin