Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Locally scoped data with CompositionLocal

CompositionLocal, Composition üzerinden örtük olarak veri aktarmaya yarayan bir araçtır. Bu sayfada, CompositionLocal’ın ne olduğunu daha ayrıntılı olarak öğrenecek, kendi CompositionLocal’ınızı nasıl oluşturacağınızı öğrenecek ve CompositionLocal’ın kullanım durumunuz için iyi bir çözüm olup olmadığını anlayacaksınız.

Introducing CompositionLocal

Genellikle Compose’da veri aşağı akar UI ağacı üzerinden her bir composable fonksiyona parametre olarak aktarılır. Bu, bir composable’ın bağımlılıklarını açık hale getirir. Ancak bu, renkler veya yazı 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() {
    // Tema bilgileri uygulamanın root'una yakın bir yerde tanımlanma eğilimindedir
    val colors = colors()
}

// Hiyerarşinin derinliklerinde bazı composablelar
@Composable
fun SomeTextLabel(labelText: String) {
    Text(
        text = labelText,
        color = colors.onPrimary // ← renklere buradan erişmeniz gerekiyor
    )
}

Renklerin çoğu composable’a açık bir parametre bağımlılığı olarak aktarılmasına gerek kalmamasını desteklemek için Compose, UI ağacından veri akışı sağlamak için örtük bir yol olarak kullanılabilecek ağaç kapsamına alınmış adlandırılmış nesneler oluşturmanıza olanak tanıyan CompositionLocal özelliğini sunar.

CompositionLocal öğeleri genellikle UI ağacının belirli bir düğümünde bir değerle sağlanır. Bu değer, CompositionLocal öğesini composable fonksiyonda bir parametre olarak bildirmeden, composable torunları tarafından kullanılabilir.

Anahtar terimler: Bu kılavuzda Composition, UI tree ve UI hierarchy terimlerini kullanıyoruz. Diğer kılavuzlarda birbirleriyle değiştirilebilir şekilde kullanılsalar da farklı anlamlara sahiptirler: Composition, composable fonksiyonların çağrı grafiğinin kaydıdır. UI ağacı veya UI hiyerarşisi, composition işlemi tarafından oluşturulan, güncellenen ve bakımı yapılan LayoutNode ağacıdır.

CompositionLocal, Material temasının kaputun altında kullandığı şeydir. MaterialTheme üç CompositionLocal instance’ı (renkler, tipografi ve şekiller) sağlayan bir nesnedir ve bunları daha sonra Composition’ın herhangi bir alt kısmında almanıza olanak tanır. Bunlar özellikle MaterialTheme renkler, şekiller ve tipografi nitelikleri aracılığıyla erişebileceğiniz LocalColors, LocalShapes ve LocalTypography propertyleridir.

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

Bir CompositionLocal instance, Composition’ın bir bölümüne kapsamlandırılmıştır, böylece ağacın farklı seviyelerinde farklı değerler sağlayabilirsiniz. Bir CompositionLocalın [current][(https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal#current()) değeri, Composition’ın o bölümündeki bir ata tarafından sağlanan en yakın değere karşılık gelir.

Bir CompositionLocal’a yeni bir değer sağlamak için CompositionLocalProvider‘ı ve CompositionLocal anahtarını bir değerle ilişkilendiren provides infix fonksiyonunu kullanın. CompositionLocalProvider’ın content lambda’sı, CompositionLocal’ın current property’sine erişirken sağlanan değeri alacaktır. Yeni bir değer sağlandığında, Compose CompositionLocal’ı okuyan Composition parçalarını yeniden oluşturur.

Bunun bir örneği olarak, LocalContentAlpha CompositionLocal, UI’nin farklı bölümlerini belirginleştirmek veya belirginliğini azaltmak amacıyla metin ve ikonografi için kullanılan preferred content alpha değerini içerir. Aşağıdaki örnekte, CompositionLocalProvider Composition’ın farklı bölümleri için farklı değerler sağlamak üzere kullanılmaktadır.

@Composable
fun CompositionLocalExample() {
    MaterialTheme { // MaterialTheme ContentAlpha.high değerini varsayılan olarak ayarlar
        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 ayrıca composable fonksiyonlar arasında da çalışır
    Text("This Text uses the disabled alpha now")
}

Şekil 1. CompositionLocalExample composable önizlemesi.

Yukarıdaki tüm örneklerde, CompositionLocal instance’ları Material composable’ları tarafından dahili olarak kullanılmıştır. Bir CompositionLocal’ın geçerli değerine erişmek için current propertysini kullanın. Aşağıdaki örnekte, metni biçimlendirmek için Android uygulamalarında yaygın olarak kullanılan LocalContext CompositionLocal’ın geçerli Context değeri kullanılmıştır:

@Composable
fun FruitText(fruitSize: Int) {
    // LocalContext'in current değerinden `resources` öğesini alır
    val resources = LocalContext.current.resources
    val fruitText = remember(resources, fruitSize) {
        resources.getQuantityString(R.plurals.fruit_title, fruitSize)
    }
    Text(text = fruitText)
}

Not: CompositionLocal nesneleri veya sabitleri, IDE’de otomatik tamamlama ile daha iyi bulunabilirlik sağlamak için genellikle Local ile ön ek alır.

Creating your own CompositionLocal

CompositionLocal, verilerin Composition boyunca dolaylı olarak aktarılması için bir araçtır.

CompositionLocal kullanımı için bir diğer önemli sinyal, parametrenin cross-cutting olduğu ve ara uygulama katmanlarının bunun varlığından haberdar olmaması gerektiğidir, çünkü bu ara katmanların haberdar olması composable’ın faydasını sınırlayacaktır. Örneğin, Android izinleri için sorgulama, kaputun altındaki bir CompositionLocal tarafından sağlanır. Composable bir medya seçici, API’sini değiştirmeden ve medya seçiciyi call edenlerin environment’dan kullanılan bu ek context’ten haberdar olmasını gerektirmeden cihazdaki izin korumalı içeriğe erişmek için yeni fonksiyonlar ekleyebilir.

Ancak, CompositionLocal her zaman en iyi çözüm değildir. Bazı dezavantajları olduğu için CompositionLocal’ın aşırı kullanımını önermiyoruz:

CompositionLocal, bir composable’ın davranışı hakkında mantık yürütmeyi zorlaştırır. Örtük bağımlılıklar yarattıklarından, bunları kullanan composable’ları çağıranların her CompositionLocal için bir değerin karşılandığından emin olmaları gerekir.

Ayrıca, Composition’ın 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, mevcut değerin nerede sağlandığını görmek için Composition’da gezinmeniz gerektiğinden, bir sorun oluştuğunda uygulamada hata ayıklama yapmak daha zor olabilir. IDE’deki Find usages veya Compose layout inspector gibi araçlar bu sorunu hafifletmek için yeterli bilgi sağlar.

Not: CompositionLocal, temel mimari için iyi çalışır ve Jetpack Compose bunu yoğun bir şekilde kullanır.

Deciding whether to use CompositionLocal

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

Bir CompositionLocal iyi bir varsayılan değere sahip olmalıdır. Varsayılan bir değer yoksa, bir geliştiricinin CompositionLocal için bir değerin sağlanmadığı bir duruma girmesinin son derece zor olduğunu garanti etmelisiniz. Varsayılan bir değer sağlamamak, CompositionLocal’in her zaman açıkça sağlanmasını gerektiren bir composable’ı kullanan testler oluştururken veya önizleme yaparken sorunlara ve hayal kırıklığına neden olabilir.

Ağaç kapsamı veya alt hiyerarşi kapsamı olarak düşünülmeyen kavramlar için CompositionLocal kullanmaktan kaçının. Bir CompositionLocal, birkaçı tarafından değil, herhangi bir torun tarafından potansiyel olarak kullanılabildiğinde anlamlıdır.

Kullanım durumunuz bu gereksinimleri karşılamıyorsa, bir CompositionLocal oluşturmadan önce Dikkate Alınacak Alternatifler bölümüne göz atın.

Belirli bir ekranın ViewModel’ini tutan bir CompositionLocal oluşturmak kötü bir pratiğe örnektir, böylece o ekrandaki tüm composable’lar bazı mantıkları gerçekleştirmek için ViewModel’e bir referans alabilir. Bu kötü bir pratiktir çünkü belirli bir UI ağacının altındaki tüm composable’ların bir ViewModel hakkında bilgi sahibi olması gerekmez. İyi pratik, state’in aşağı ve event’lerin yukarı akması modelini izleyerek composable’lara yalnızca ihtiyaç duydukları bilgileri aktarmaktır. Bu yaklaşım, composable’larınızı daha yeniden kullanılabilir ve daha kolay test edilebilir hale getirecektir.

Creating a CompositionLocal

Bir CompositionLocal oluşturmak için iki API vardır:

  • compositionLocalOf: Recomposition sırasında sağlanan değerin değiştirilmesi, yalnızca current değerini okuyan içeriği geçersiz kılar.

  • staticCompositionLocalOf: compositionLocalOf’un aksine, bir staticCompositionLocalOf’un okumaları Compose tarafından izlenmez. Değerin değiştirilmesi, CompositionLocal’ın sağlandığı content lambda’nın tamamının, sadece geçerli değerin Composition’da okunduğu yerler yerine yeniden oluşturulmasına neden olur.

CompositionLocal’a sağlanan değerin değişme olasılığı çok düşükse veya hiç değişmeyecekse, performans avantajları elde etmek için staticCompositionLocalOf kullanın.

Örneğin, bir uygulamanın tasarım sistemi, composable’ların UI komponenti için bir gölge kullanılarak elevate edilmesi konusunda fikir sahibi olabilir. Uygulama için farklı elevate etmelerin UI ağacı boyunca yayılması gerektiğinden, bir CompositionLocal kullanırız. CompositionLocal değeri sistem temasına göre koşullu olarak türetildiğinden, compositionLocalOf API’sini kullanırız:

// LocalElevations.kt dosyasi

data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp)

// Varsayılan bir CompositionLocal global nesnesi tanımlayın
// Bu instance'a uygulamadaki tüm composable'lar tarafından erişilebilir
val LocalElevations = compositionLocalOf { Elevations() }

Providing values to a CompositionLocal

CompositionLocalProvider composable, değerleri verilen hiyerarşi için CompositionLocal instance’larına bağlar. Bir CompositionLocal’a yeni bir değer sağlamak için, bir CompositionLocal key’ini bir değerle ilişkilendiren provides infix fonksiyonunu aşağıdaki gibi kullanın:

// MyActivity.kt file

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            // Sistem temasına göre yükseklikleri hesaplayın
            val elevations = if (isSystemInDarkTheme()) {
                Elevations(card = 1.dp, default = 1.dp)
            } else {
                Elevations(card = 0.dp, default = 0.dp)
            }

            // Yüksekliği LocalElevations için değer olarak bağlayın
            CompositionLocalProvider(LocalElevations provides elevations) {
                // ... Content buraya gidecek ...
                // Composition'ın bu bölümü LocalElevations.current dosyasına erişirken 
                // `elevations` instance'ını görecektir
            }
        }
    }
}

Consuming the CompositionLocal

CompositionLocal.current, o 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() {
    // Composition'ın bu bölümündeki mevcut Elevations değerini almak için 
    // global olarak tanımlanmış LocalElevations değişkenine erişin
    Card(elevation = LocalElevations.current.card) {
        // Content
    }
}

Alternatives to consider

CompositionLocal bazı kullanım durumları için aşırı bir çözüm olabilir. Kullanım durumunuz CompositionLocal kullanıp kullanmamaya karar verme bölümünde belirtilen ölçütleri karşılamıyorsa, kullanım durumunuz için başka bir çözüm daha uygun olabilir.

Pass explicit parameters

Composable’ın bağımlılıkları hakkında açık olmak iyi bir alışkanlıktır. Composable’lara yalnızca ihtiyaç duydukları bilgileri aktarmanızı öneririz. Composable’ların decoupling ve yeniden kullanımını teşvik etmek için, her composable mümkün olan en az miktarda bilgiyi tutmalıdır.

@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
    // ...
    MyDescendant(myViewModel.data)
}

// Tüm nesneyi geçirmeyin! Sadece torunun ihtiyacı olanı.
// Ayrıca, bir CompositionLocal kullanarak ViewModel'i örtük bir bağımlılık olarak geçirmeyin.
@Composable
fun MyDescendant(myViewModel: MyViewModel) { /* ... */ }

// Sadece torunun ihtiyacı olanı gecirin
@Composable
fun MyDescendant(data: DataToDisplay) {
    // Verileri görüntüle
}

Inversion of control

Gereksiz bağımlılıkları bir composable’a geçirmekten kaçınmanın bir başka yolu da inversion of control’dür. Alt öğenin bazı logicleri yürütmek için bir bağımlılık alması yerine, üst öğe 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 çok fazla sorumluluğa sahip olabilir. Ayrıca, MyViewModel’i bir bağımlılık olarak geçirmek, MyDescendant’ı artık birbirine bağlı oldukları için daha az yeniden kullanılabilir hale getirir. Bağımlılığı toruna geçirmeyen ve logic’in yürütülmesinden atayı sorumlu kılan inversion of control ilkelerini kullanan alternatifi 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, çocuğu yakın atalarından decouple ettigi için bazı kullanım durumları için daha uygun olabilir. Ata composablelar, daha esnek alt seviye composablelara sahip olmak için daha karmaşık hale gelme eğilimindedir.

Benzer şekilde, @Composable content lambda’lar da aynı faydaları 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()
    }
}