CompositionLocal ile yerel kapsamlı veriler

CompositionLocal, verileri kompozisyon üzerinden dolaylı olarak iletmek için kullanılan bir araçtır. Bu sayfada CompositionLocal öğesinin ne olduğunu daha ayrıntılı olarak, kendi şablonunuzu nasıl oluşturacağınızı öğrenin CompositionLocal ve CompositionLocal çözümünün aşağıdakiler için iyi bir çözüm olup olmadığını öğrenin ne kadar iyi karşıladığını görebileceksiniz.

CompositionLocal ile tanışın

Compose'da genellikle veriler akıştan Her composable işlevin parametreleri olarak kullanıcı arayüzü ağacı. Bu, bir composable'ın açıklığa kavuşturmaktır. Ancak bu, çok büyük boyutlu ve yaygın olarak kullanılır. 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 Compose teklifleri CompositionLocal sayesinde kullanarak örtülü bir yöntem olarak kullanılabilecek ağaç kapsamlı adlandırılmış nesneler oluşturmak kullanıcı arayüzü ağacı üzerinden veri akışı sağlanıyor.

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, composable alt öğeleri tarafından CompositionLocal, composable işlevde parametre olarak tanımlanıyor.

CompositionLocal, Material temasının temelinde kullanılan öğedir. MaterialTheme, colorScheme, typography ve shapes olmak üzere üç CompositionLocal örneği sağlayan bir nesnedir. Bu örnekleri daha sonra bileşimin herhangi bir alt öğesinde alabilirsiniz. Daha açık belirtmek gerekirse, MaterialThemecolorScheme, 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üne göre kapsamlandırılır. Böylece ağacın farklı seviyelerinde farklı değerler sağlayabilirsiniz. current değeri bir CompositionLocal, üst öğe olabilir.

Bir CompositionLocal için yeni bir değer sağlamak istiyorsanız CompositionLocalProvider ve CompositionLocal anahtarını value ile ilişkilendiren provides ara değer işlevini kullanın. CompositionLocalProvider'un content lambdası, CompositionLocal'ın current özelliğine erişirken sağlanan değeri alır. Bir yeni değer sağlandığında, Compose, Beste'nin şu okunan kısımlarını yeniden CompositionLocal.

Buna bir örnek olarak, LocalContentColor CompositionLocal metin ve kullanarak mevcut arka plan rengiyle kontrast oluşturun. aşağıdaki örnekte CompositionLocalProvider, farklı erişim düzeyleri sağlamak için değerleri oluşturabilirsiniz.

@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")
}

Şekil 1. CompositionLocalExample composable'ın ö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 geçerli Context değeri metni biçimlendirmek için kullanılı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 aşağıya veri aktarmak için kullanılan bir araçtır dolaylı olarak oluşturabilirsiniz.

CompositionLocal kullanmak için bir diğer önemli sinyal, parametrenin kesişim ve ara uygulama katmanlarının farkında olmalısınız. vardır, çünkü bu ara katmanların farkında olmaları fayda sağlar. Örneğin, Android izinleri için sorgu, temelde bir CompositionLocal tarafından sağlanır. Bir medya seçici bileşeni, API'sini değiştirmeden ve medya seçiciyi çağıranların ortamda 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. Biz Bazı dezavantajları beraberinde getirdiği için CompositionLocal'ın aşırı kullanımının önüne geçin:

CompositionLocal, bir composable'ın davranışının akıl yürütmesini zorlaştırır. Farklı örtülü bağımlılıklar oluşturuyorlar. Bunları kullanan composable’ları arayanlar her CompositionLocal için bir değere uygun olduğundan emin olun.

Ayrıca, bu bağımlılık, kompozisyonun herhangi bir yerinde mutasyona uğrayabileceğ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 kompozisyonda yukarı doğru gitmeniz gerektiğinden sorun oluştuğunda uygulamada hata ayıklama işlemi daha zor olabilir. Find gibi araçlar kullanımları IDE veya Oluşturma düzen denetleyicisi'ndeki olabileceğini unutmayın.

CompositionLocal kullanılıp kullanılmayacağına karar veriliyor

CompositionLocal'ü kullanım alanınız için iyi bir çözüm haline getirebilecek belirli koşullar vardır:

CompositionLocal'in iyi bir varsayılan değeri olmalıdır. Varsayılan değer yoksa geliştiricilerin CompositionLocal için bir değer sağlamadığı bir duruma düşmelerinin son derece zor olduğunu garanti etmeniz gerekir. Varsayılan değer sağlamamak, test oluştururken veya bu özelliği kullanan bir bileşeni önizlerken sorunlara ve can sıkıcı durumlara neden olabilir. CompositionLocal her zaman açıkça sağlanmalıdır.

Ağaç kapsamlı veya ağaç kapsamlı olarak düşünülmeyen kavramlar için CompositionLocal alt hiyerarşi kapsamındaki CompositionLocal, mümkün olduğunda anlam ifade eder birkaç alt öğe tarafından kullanıldığı düşünülebilir.

Kullanım alanınız bu koşulları karşılamıyorsa CompositionLocal oluşturmadan önce Dikkate alınacak alternatifler bölümüne göz atın.

Kötü uygulamalara örnek olarak,CompositionLocal Belirli bir ekranın ViewModel kadarını ekleyebilirsiniz. Böylece o ekrandaki tüm composable'lar, bir mantık yürütmek için ViewModel öğesine referans alın. Bu kötü bir uygulama çünkü belirli bir kullanıcı arayüzü ağacının altındaki tüm composable'ların ViewModel İyi uygulama, yalnızca composable'lara bilgileri durumun aşağı, etkinliklerin yukarı akışı kalıbına uymaları gerekir. Bu yaklaşım, composable'larınızı daha verimli ve daha kolay test edilebilir.

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ızca geçersiz kılınır şu mesajı okuyan içerik: current değer.

  • staticCompositionLocalOf: compositionLocalOf işlevinin aksine, staticCompositionLocalOf okumaları Compose tarafından takip edilir. Değerin değiştirilmesi, current değerinin Kompozisyon'da okunduğu yerlerin yerine CompositionLocal değerinin sağlandığı content lambda'sının tamamının yeniden derlenmesine neden olur.

CompositionLocal için sağlanan değerin değişme olasılığı düşükse veya asla değişmeyecek. Performans avantajlarından yararlanmak için staticCompositionLocalOf kullanın.

Örneğin, bir uygulamanın tasarım sistemi, composable'ların değeri, kullanıcı arayüzü bileşeni için bir gölge kullanılarak yükseltilir. Uygulamanın farklı yükseklikleri kullanıcı arayüzü ağacı boyunca yayılacağından bir CompositionLocal kullanırız. CompositionLocal değeri koşullu olarak türetildiğinden sistem temasına göre compositionLocalOf API'yi kullanıyoruz:

// 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 bileşeni, değerleri belirli bir hiyerarşi için CompositionLocal örneklerine bağlar. Bir CompositionLocal için yeni bir değer sağlamak üzere, CompositionLocal anahtarını value ile ilişkilendiren provides ara dize 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üketme

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
    MyCard(elevation = LocalElevations.current.card) {
        // Content
    }
}

Değerlendirebileceğiniz alternatifler

CompositionLocal, bazı kullanım alanları için aşırı bir çözüm olabilir. Kullanım alanınız CompositionLocal'ı kullanıp kullanmayacağınıza karar verme bölümünde belirtilen ölçütleri karşılamıyorsa kullanım alanınıza daha uygun başka bir çözüm olabilir.

Belirli parametreleri iletme

Kompozit'in bağımlılıkları konusunda net olmak iyi bir alışkanlıktır. Kompozitlere yalnızca ihtiyaç duydukları bilgileri iletmenizi öneririz. Birleştirilebilir öğelerin ayrılmasını ve yeniden kullanılmasını teşvik etmek için her bir birleştirilebilir öğe mümkün olan en az miktarda 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 bileşene gereksiz bağımlılıkların aktarılmasını önlemenin bir diğer yolu da kontrolün tersine çevrilmesidir. Bir bağımlılığın yerine ana yayıncı bir mantık yürütür. Bunun yerine ana yayıncı 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'ün çok fazla sorumluluğu olabilir. Ayrıca, MyViewModel bağımlılığı nedeniyle MyDescendant daha az yeniden kullanılabilir çünkü Bunlar artık birbiriyle ilişkilendirildi. Şunlardan geçemeyen alternatifi düşünün: alt türe bağımlılığı artırır ve kontrol ilkelerinin tersini kullanır. üst öğeyi mantığı yürütmekten sorumlu hale getirir:

@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ığından bazı kullanım alanları için daha uygun olabilir. Üst öğe derlemeleri, daha esnek alt düzey derlemelere sahip olmak için daha karmaşık hale gelir.

Benzer şekilde, @Composable içerik lambda'ları da aynı avantajlardan yararlanmak 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()
    }
}