CompositionLocal
, besteden dolaylı olarak
veri aktarmak için kullanılan bir araçtır. Bu sayfada CompositionLocal
öğesinin ne olduğunu, kendi CompositionLocal
öğenizi nasıl oluşturacağınızı ve CompositionLocal
öğesinin kullanım alanınız için iyi bir çözüm olup olmadığını öğreneceksiniz.
CompositionLocal
ile tanışın
Genellikle Compose'da veriler, kullanıcı arayüzü ağacından her bir composable işlevin parametre olarak aşağıya aktarılır. Bu, bir composable'ın bağımlılıklarını açık hale getirir. Ancak bu, renkler ve tür stilleri gibi çok sık ve yaygın bir şekilde kullanılan veriler için külfetli 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 composable'a açık bir parametre olarak iletme ihtiyacını ortadan kaldırmak için Compose, kullanıcı arayüzü ağacında veri akışı sağlamanın dolaylı bir yolu olarak kullanılabilecek ağaç kapsamlı adlandırılmış nesneler oluşturmanıza olanak tanıyan CompositionLocal
sunar.
CompositionLocal
öğeleri, kullanıcı arayüzü ağacının belirli bir düğümünde genellikle bir değerle sağlanır. Bu değer, CompositionLocal
öğesini composable işlevinde parametre olarak bildirmeden composable alt öğeleri tarafından kullanılabilir.
Materyal teması, arka planda CompositionLocal
kullanır.
MaterialTheme
, üç CompositionLocal
örneği (renkler, tipografi ve şekiller) sağlayan bir nesnedir. Böylece, bu örnekleri daha sonra Beste'nin herhangi bir alt bölümünde alabilirsiniz. MaterialTheme
colors
, shapes
ve typography
özellikleri üzerinden erişebileceğiniz LocalColors
, LocalShapes
ve LocalTypography
özellikleri şunlardır.
@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 ) }
Ağacın farklı düzeylerinde farklı değerler sağlayabilmeniz için CompositionLocal
örneği Beste'nin bir bölümüne dahil edilir. CompositionLocal
öğesinin current
değeri, Beste'nin söz konusu bölümündeki bir üst öğe 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
öğ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
bölümünü okuyan Beste bölümlerini yeniden oluşturur.
Buna örnek olarak LocalContentAlpha
CompositionLocal
, kullanıcı arayüzünün farklı bölümlerini vurgulamak veya vurgulamak için metin ve ikonografide kullanılan, tercih edilen alfa içeriğini içerir. Aşağıdaki örnekte, Beste'nin farklı bölümleri için farklı değerler sağlamak amacıyla CompositionLocalProvider
kullanılmıştır.
@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") }
Şekil 1. CompositionLocalExample
composable'ın önizlemesi.
Yukarıdaki tüm örneklerde CompositionLocal
örnekleri, Material composable'ları 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
oluşturma
CompositionLocal
, Beste'den dolaylı olarak veri 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 izinlerini sorgulama, arka planda bir CompositionLocal
tarafından sağlanır. Bir medya seçici composable, API'sini değiştirmeden ve medya seçme aracını çağıran kişilerin ortamdan kullanılan bu ek bağlamı bilmelerini gerektirmeden cihazdaki izne korunan 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
ürününün aşırı kullanılmamasını öneriyoruz:
CompositionLocal
, bir composable'ın davranışını akıl yürütmeyi zorlaştırır. Dolaylı bağımlılıklar oluşturdukları için, composable'ları kullanan composable'ları arayanların, her CompositionLocal
için bir değerin memnun olduğundan emin olmaları gerekir.
Dahası, bu bağımlılığın bestenin herhangi bir bölümünde değişebileceği için açık ve doğru bir kaynak 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ımları bul veya Oluşturma düzen denetleyicisi gibi araçlar bu sorunu azaltmak için yeterli bilgi sağlar.
CompositionLocal
kullanılıp kullanılmayacağına karar veriliyor
CompositionLocal
ürününü kullanım alanınız için 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 bir değerin sağlanmadığı bir duruma düşmesinin çok zor olacağını garanti etmeniz gerekir.
Varsayılan bir değer sağlanmaması, test oluştururken veya CompositionLocal
öğesini kullanan bir composable'ın önizlenirken her zaman açıkça sağlanmasını gerektirirken sorun ve hayal kırıklığına yol açabilir.
Ağaç kapsamlı veya alt hiyerarşi kapsamlı olarak düşünülmeyen kavramlar için CompositionLocal
kullanmaktan kaçının. CompositionLocal
, birkaç alt öğe tarafından değil, herhangi bir alt öğe tarafından kullanılabildiğinde 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, mantıksal hareket etmek için ViewModel
öğesine referans verebilir. Belirli bir kullanıcı arayüzü ağacının altındaki tüm composable'ların ViewModel
hakkında bilgi sahibi olması gerekmediğinden bu kötü bir uygulamadır. İyi bir uygulama, composable'lara yalnızca ihtiyaç duydukları bilgileri durum aşağı, etkinliklerin yukarı aktığı kalıbı izlenerek iletmektir. Bu yaklaşım, composable'larınızı daha çok
kullanılabilir ve test edilmesini kolaylaştıracak.
CompositionLocal
oluşturuluyor
CompositionLocal
oluşturmak için iki API vardır:
compositionLocalOf
: Yeniden oluşturma sırasında sağlanan değerin değiştirilmesi, yalnızcacurrent
değerini okuyan içeriği geçersiz kılar.staticCompositionLocalOf
:compositionLocalOf
öğesinin aksine,staticCompositionLocalOf
okumaları Compose tarafından takip edilmez. Değerin değiştirilmesi, yalnızcacurrent
değerinin Beste'de okunduğu yerler yerine,CompositionLocal
öğesinin sağlandığıcontent
lambdasının tamamının yeniden oluşturulmasına neden olur.
CompositionLocal
için sağlanan değerin değişme olasılığı yoksa veya hiçbir zaman değişmeyecekse performans avantajlarından yararlanmak için staticCompositionLocalOf
değerini kullanın.
Örneğin, bir uygulamanın tasarım sistemi, kullanıcı arayüzü bileşeni için bir gölge kullanılarak composable'ların yükseltilme biçiminde değerlendirilebilir. Uygulamanın farklı yükseltmelerinin kullanıcı arayüzü ağacına yayılması gerektiği için CompositionLocal
kullanılır. CompositionLocal
değeri sistem temasına göre koşullu olarak türetildiğinden compositionLocalOf
API'yi 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
composable, 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
kullanımı
CompositionLocal.current
, söz konusu CompositionLocal
için bir 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 Card(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 başka bir çözüm muhtemelen kullanım alanınız için daha uygun olabilir.
Açık parametreleri iletme
composable'ın bağımlılıklarını açıkça belirtmek iyi bir alışkanlıktır. composable'ları 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ü ters çevirme
Gereksiz bağımlılıkları bir composable'a iletmekten kaçınmanın bir başka yolu da kontrolü ters çevirmektir. Alt öğe bir mantık yürütmek için bağımlılığı almak yerine 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
adlı kuruluşun büyük sorumluluğu olabilir. Ayrıca, bağımlılık olarak MyViewModel
'ın iletilmesi, artık birbiriyle ilişkili oldukları için MyDescendant
ürününün yeniden kullanılamaz hale gelmesini sağlar. Bağımlılığı alt öğeye iletmeyen ve üst öğeyi mantığı yürütmekten sorumlu yapan kontrol ilkelerinin tersini kullanan bir alternatif 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 kendi atalarından ayırdığı için bazı kullanım alanları için daha uygun olabilir. Üst composable'lar, daha esnek ve düşük seviyeli composable'lara sahip oldukları için daha karmaşık hâle gelme eğilimindedir.
Benzer şekilde, @Composable
içerik lambda'ları 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'da bir temanın anatomisi
- Compose'da Görünümleri kullanma
- Jetpack Compose için Kotlin