CompositionLocal
, verileri Composition'dan aşağıya ö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, her bir composable işlevine parametre olarak kullanıcı arayüzü ağacından aşağı doğru akar. Bu, composable'ın bağımlılıklarını açık hale getirir. Ancak bu, 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 bir parametre bağımlılığı olarak iletilmesine gerek olmaması için Compose, CompositionLocal
sunar. Bu, verilerin kullanıcı arayüzü ağacında akmasını sağlamak için örtülü bir yol olarak 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 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ığı öğedir.
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. 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") }
1.şekil 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ı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 CompositionLocal
oluşturma
CompositionLocal
, verileri Kompozisyon aracılığıyla dolaylı olarak aktarmak için kullanılan bir araçtır.
CompositionLocal
kullanmanın bir diğer önemli sinyali, parametrenin kesişen bir parametre olması ve bu parametrenin varlığının ara uygulama katmanları tarafından bilinmemesi gerektiğidir. Çünkü bu ara katmanların parametrenin varlığından 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ç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ı, bazı dezavantajları olduğu için ö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ğru 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 kullanmamaya 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ğlanmayan 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, 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 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 composable'lara yalnızca ihtiyaç duydukları 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ızcacurrent
değ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
değerinin sağlandığıcontent
lambda'nın tamamının, yalnızcacurrent
değ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ğer sağlama
CompositionLocalProvider
Composable'ı, belirli bir hiyerarşi için değerleri CompositionLocal
örneklerine bağlar. Bir CompositionLocal
öğesine yeni bir değer sağlamak için 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
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 çevrilmesidir. 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. Bunun nedeni, bu öğelerin artık birlikte kullanılmasıdır. 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ı ş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'daki temanın anatomisi
- Oluşturma penceresinde görünümleri kullanma
- Jetpack Compose için Kotlin