State ve Jetpack Compose

Bir uygulamadaki durum, zaman içinde değişebilen herhangi bir değerdir. Bu çok geniş bir tanımdır ve Room veritabanından sınıftaki bir değişkene kadar her şeyi kapsar.

Tüm Android uygulamaları, durumu kullanıcıya gösterir. Android uygulamalarındaki duruma dair birkaç örnek:

  • Ağ bağlantısı kurulamadığında gösterilen bir bilgi çubuğu.
  • Bir blog yayını ve ilişkili yorumlar.
  • Kullanıcı düğmeleri tıkladığında oynatılan dalga animasyonlar.
  • Kullanıcının bir resmin üzerine çizebileceği çıkartmalar.

Jetpack Compose, Android uygulamasında durumu nerede ve nasıl depolayıp kullanacağınız konusunda net olmanıza yardımcı olur. Bu kılavuzda, durum ile bileşenler arasındaki bağlantıya ve Jetpack Compose'un durumla daha kolay çalışmak için sunduğu API'lere odaklanılmaktadır.

Durum ve kompozisyon

Oluşturma beyandır. Bu nedenle, tek güncelleme yöntemi, aynı derlenebilir öğeyi yeni bağımsız değişkenlerle çağırmaktır. Bu bağımsız değişkenler, kullanıcı arayüzü durumunu temsil eder. Bir durum her güncellendiğinde yeniden düzenleme gerçekleştirilir. Sonuç olarak, TextField gibi öğeler, zorunlu XML tabanlı görünümlerde olduğu gibi otomatik olarak güncellenmez. Bir bileşenin uygun şekilde güncellenebilmesi için yeni durumun açıkça belirtilmesi gerekir.

@Composable
private fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello!",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(
            value = "",
            onValueChange = { },
            label = { Text("Name") }
        )
    }
}

Bu kodu çalıştırıp metin girmeye çalışırsanız hiçbir şey olmadığını görürsünüz. Bunun nedeni, TextField öğesinin kendisini güncellememesidir, value parametresi değiştiğinde güncellenir. Bunun nedeni, Oluştur'da derleme ve yeniden derlemenin işleyiş şeklidir.

İlk kompozisyon ve yeniden kompozisyon hakkında daha fazla bilgi edinmek için Oluşturma modunda düşünme başlıklı makaleyi inceleyin.

Birleştirilebilir öğelerdeki durum

Birleştirilebilir işlevler, bir nesneyi bellekte depolamak için remember API'sini kullanabilir. remember tarafından hesaplanan bir değer, ilk kompozisyon sırasında kompozisyonda depolanır ve depolanan değer yeniden kompozisyon sırasında döndürülür. remember, hem değişken hem de sabit nesneleri depolamak için kullanılabilir.

mutableStateOf, MutableState<T> adlı gözlemlenebilir bir tür oluşturur. Bu tür, derleme çalışma zamanında entegre edilmiş bir gözlemlenebilir türdür.

interface MutableState<T> : State<T> {
    override var value: T
}

value'te yapılan değişiklikler, value değerini okuyan tüm derlenebilir işlevlerin yeniden derlenmesini planlar.

Bir bileşende MutableState nesnesi tanımlamanın üç yolu vardır:

  • val mutableState = remember { mutableStateOf(default) }
  • var value by remember { mutableStateOf(default) }
  • val (value, setValue) = remember { mutableStateOf(default) }

Bu bildirimler eşdeğerdir ve durumun farklı kullanımları için söz dizimi şekeri olarak sağlanır. Yazdığınız bileşende en kolay okunur kodu üreten seçeneği tercih etmeniz gerekir.

by yetki verilmiş kullanıcı söz dizimi için aşağıdaki içe aktarma işlemleri gerekir:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

Hatırlanan değeri diğer composable'lar için parametre olarak, hatta hangi composable'ların görüntüleneceğini değiştirmek için ifadelerde mantık olarak kullanabilirsiniz. Örneğin, ad boşsa karşılama mesajını görüntülemek istemiyorsanız durumu bir if ifadesinde kullanın:

@Composable
fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        var name by remember { mutableStateOf("") }
        if (name.isNotEmpty()) {
            Text(
                text = "Hello, $name!",
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}

remember, yeniden oluşturma işlemlerinde durumu korumanıza yardımcı olsa da yapılandırma değişikliklerinde durum korunmaz. Bunun için rememberSaveable kullanmanız gerekir. rememberSaveable, Bundle içine kaydedilebilecek tüm değerleri otomatik olarak kaydeder. Diğer değerler için özel bir tasarruf nesnesi iletebilirsiniz.

Desteklenen diğer durum türleri

Compose, durum bilgisini korumak için MutableState<T> kullanmanızı gerektirmez. Diğer gözlemlenebilir türleri destekler. Compose'da başka bir gözlemlenebilir türü okumadan önce, durum değiştiğinde birleştirilebilirlerin otomatik olarak yeniden derlenebilmesi için türü State<T> olarak dönüştürmeniz gerekir.

Android uygulamalarında kullanılan yaygın gözlemlenebilir türlerden State<T> oluşturmak için işlevlerle birlikte gönderilir. Bu entegrasyonları kullanmadan önce aşağıda açıklandığı şekilde uygun öğeleri ekleyin:

  • Flow: collectAsStateWithLifecycle()

    collectAsStateWithLifecycle(), Flow kaynağından yaşam döngüsüne duyarlı bir şekilde değer toplayarak uygulamanızın uygulama kaynaklarını korumasını sağlar. Oluştur'dan State gönderilen en son değeri temsil eder. Android uygulamalarında akışları toplamak için önerilen yol olarak bu API'yi kullanın.

    build.gradle dosyasında aşağıdaki bağımlılığın bulunması gerekir (2.6.0-beta01 veya daha yeni bir sürüm olmalıdır):

Kotlin

dependencies {
      ...
      implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}

Groovy

dependencies {
      ...
      implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
  • Flow: collectAsState()

    collectAsState, Flow'den değerler toplayıp Oluştur State işlevine dönüştürdüğü için collectAsStateWithLifecycle'e benzer.

    Yalnızca Android için olan collectAsStateWithLifecycle yerine platformdan bağımsız kod için collectAsState kullanın.

    collectAsState, compose-runtime'te kullanılabildiği için ek bağımlılıklara ihtiyaç duymaz.

  • LiveData: observeAsState()

    observeAsState(), bu LiveData öğesini gözlemlemeye başlar ve değerlerini State aracılığıyla temsil eder.

    build.gradle dosyasında aşağıdaki bağımlılık zorunludur:

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-livedata:1.7.5")
}

Eski

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-livedata:1.7.5"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.7.5")
}

Groovy

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.7.5"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava3:1.7.5")
}

Groovy

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava3:1.7.5"
}

Durum bilgili ve durum bilgisiz

Bir nesneyi depolamak için remember kullanan bir bileşen, dahili durum oluşturur ve bileşeni durumlu hale getirir. HelloContent, name durumunu dahili olarak saklayıp değiştirdiği için durum bilgisine sahip bir bileşen örneğidir. Bu, arayan kişinin durumu kontrol etmesine gerek olmadığı ve durumu yönetmek zorunda kalmadan kullanabildiği durumlarda yararlı olabilir. Ancak dahili durumu olan bileşenler daha az yeniden kullanılabilir ve test edilmesi daha zordur.

Durumsuz bir bileşen, herhangi bir durum içermeyen bir bileşendir. Durum bilgisizliği sağlamanın kolay bir yolu durum bilgisini kaldırma özelliğini kullanmaktır.

Yeniden kullanılabilir bileşenler geliştirirken genellikle aynı bileşenin hem durum bilgisine sahip hem de durum bilgisi olmayan bir sürümünü göstermek istersiniz. Durum bilgisine sahip sürüm, durumu önemsemeyen arayanlar için kullanışlıdır. Durum bilgisiz sürüm ise durumu kontrol etmesi veya kaldırması gereken arayanlar için gereklidir.

Durum kaldırma

Compose'da durum kaldırma, bir composable'ın durum bilgisiz hale getirmek için composable'ın çağrısına durumu taşıma kalıbıdır. Jetpack Compose'da durum kaldırmayla ilgili genel kalıp, durum değişkenini iki parametreyle değiştirmektir:

  • value: T: Gösterilecek geçerli değer
  • onValueChange: (T) -> Unit: Değerin değiştirilmesini isteyen bir etkinlik. Burada T, önerilen yeni değerdir

Ancak onValueChange ile sınırlı değilsiniz. Kompozit için daha spesifik etkinlikler uygunsa bunları lambda kullanarak tanımlamanız gerekir.

Bu şekilde kaldırılan durum bazı önemli özelliklere sahiptir:

  • Tek doğru kaynak: Durumu kopyalamak yerine taşıyarak yalnızca tek bir doğru kaynak olmasını sağlıyoruz. Bu, hataları önlemeye yardımcı olur.
  • Kapsülleme: Yalnızca durum bilgisine sahip bileşimler durumlarını değiştirebilir. Tamamen dahilidir.
  • Paylaşılabilir: Kaldırılmış durum, birden fazla composable ile paylaşılabilir. name değerini farklı bir bileşende okumak isterseniz kaldırma işlemi bunu yapmanıza olanak tanır.
  • Engellenebilir: Durum bilgisi olmayan birleştirilebilir öğeleri çağıranlar, durumu değiştirmeden önce etkinlikleri yoksaymaya veya değiştirmeye karar verebilir.
  • Ayrıştırılmış: Durum bilgisiz composable'ların durumu, herhangi bir yerde depolanabilir. Örneğin, artık nameViewModel'e taşımak mümkün.

Bu örnekte, name ve onValueChange öğelerini HelloContent öğesinin dışına çıkarıp bunları ağaçta HelloContent adlı bir HelloScreen composable'a taşırsınız.

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
    }
}

Durumu HelloContent'ten kaldırarak, birleştirilebilir öğe hakkında mantık yürütmek, farklı durumlarda yeniden kullanmak ve test etmek daha kolaydır. HelloContent, durumunun depolanma şeklinden ayrılır. Ayrıştırma, HelloScreen öğesini değiştirmeniz veya değiştirmeniz durumunda HelloContent öğesinin uygulanma şeklini değiştirmeniz gerekmeyeceği anlamına gelir.

Durumun azaldığı ve etkinliklerin arttığı kalıba tek yönlü veri akışı denir. Bu durumda durum HelloScreen değerinden HelloContent değerine düşer ve etkinlikler HelloContent değerinden HelloScreen değerine yükselir. Tek yönlü veri akışını izleyerek, kullanıcı arayüzünde durumu gösteren bileşenleri, durumunu depolayan ve değiştiren uygulamanızın bölümlerinden ayırabilirsiniz.

Daha fazla bilgi için Eyalet bayrağını nereye çekmelisiniz? sayfasına bakın.

Oluşturma bölümünde durumu geri yükleme

rememberSaveable API'si, kayıtlı örnek durumu mekanizmasını kullanarak yeniden oluşturma işlemlerinde ve ayrıca etkinlik veya süreç yeniden oluşturma işlemlerinde durumu koruduğu için remember'e benzer şekilde çalışır. Örneğin, ekran döndürüldüğünde bu durumla karşılaşabilirsiniz.

Eyalet bilgisini depolamanın yolları

Bundle öğesine eklenen tüm veri türleri otomatik olarak kaydedilir. Bundle'e eklenemeyen bir öğeyi kaydetmek istiyorsanız birkaç seçeneğiniz vardır.

Paketleme

En basit çözüm, nesneye @Parcelize ek açıklama eklemektir. Nesne paketlenebilir ve gruplandırılabilir hale gelir. Örneğin, bu kodda paketlenebilir bir City veri türü oluşturulur ve duruma kaydedilir.

@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

Harita Koruyucu

@Parcelize herhangi bir nedenle uygun değilse bir nesneyi sistemin Bundle'ye kaydedebileceği bir değer kümesine dönüştürmeyle ilgili kendi kuralınızı tanımlamak için mapSaver'ü kullanabilirsiniz.

data class City(val name: String, val country: String)

val CitySaver = run {
    val nameKey = "Name"
    val countryKey = "Country"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

ListSaver

Haritanın anahtarlarını tanımlama ihtiyacını ortadan kaldırmak için listSaver'ü de kullanabilir ve dizinlerini anahtar olarak kullanabilirsiniz:

data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

Oluşturma bölümünde durum bilgisi depolayıcılar

Basit durum kaldırma işlemi, derlenebilir işlevlerin kendisinde yönetilebilir. Ancak, takip edilecek durum miktarı artarsa veya birleştirilebilir işlevlerde uygulanacak mantık ortaya çıkarsa mantık ve durum sorumluluklarını diğer sınıflara (durum tutucular) devretmek iyi bir uygulamadır.

Daha fazla bilgi edinmek için Compose'da durum kaldırma dokümanlarına veya daha genel olarak mimari kılavuzundaki Durum tutucular ve kullanıcı arayüzü durumu sayfasına göz atın.

Anahtarlar değiştiğinde hatırlama hesaplamalarını yeniden tetikleme

remember API'si genellikle MutableState ile birlikte kullanılır:

var name by remember { mutableStateOf("") }

Burada remember işlevinin kullanılması, MutableState değerinin yeniden derlemelerden etkilenmemesini sağlar.

Genel olarak remember, calculation lambda parametresi alır. remember ilk çalıştırıldığında calculation lambda'sını çağırır ve sonucunu depolar. Yeniden oluşturma sırasında remember, en son depolanan değeri döndürür.

Durumu önbelleğe almanın yanı sıra, ilk başlatılması veya hesaplanması pahalı olan herhangi bir nesneyi ya da kompozisyondaki bir işlemin sonucunu depolamak için remember'ü de kullanabilirsiniz. Bu hesaplamayı her yeniden derlemede tekrarlamak istemeyebilirsiniz. Örneğin, pahalı bir işlem olan bu ShaderBrush nesnesini oluşturabilirsiniz:

val brush = remember {
    ShaderBrush(
        BitmapShader(
            ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
            Shader.TileMode.REPEAT,
            Shader.TileMode.REPEAT
        )
    )
}

remember, değeri kompozisyondan çıkana kadar depolar. Ancak önbelleğe alınmış değeri geçersiz kılma yöntemi vardır. remember API ayrıca bir key veya keys parametresi alır. Bu anahtarlardan herhangi biri değişirse işlev bir sonraki kez yeniden derlendiğinde remember önbelleği geçersiz kılar ve hesaplama lambda bloğunu tekrar yürütür. Bu mekanizma, kompozisyondaki bir nesnenin kullanım ömrü üzerinde kontrol sahibi olmanızı sağlar. Hesaplama, hatırlanan değer kompozisyondan çıkana kadar değil, girişler değişene kadar geçerli kalır.

Aşağıdaki örneklerde bu mekanizmanın nasıl çalıştığı gösterilmektedir.

Bu snippet'te, bir ShaderBrush oluşturulur ve Box kompozit öğesinin arka plan boyası olarak kullanılır. remember, daha önce açıklandığı gibi yeniden oluşturmanın pahalı olması nedeniyle ShaderBrush örneğini saklar. remember, seçilen arka plan resmi olan key1 parametresi olarak avatarRes değerini alır. avatarRes değişirse fırça yeni resimle yeniden oluşturulur ve Box'a yeniden uygulanır. Bu durum, kullanıcı seçiciden arka plan olacak başka bir resim seçtiğinde ortaya çıkabilir.

@Composable
private fun BackgroundBanner(
    @DrawableRes avatarRes: Int,
    modifier: Modifier = Modifier,
    res: Resources = LocalContext.current.resources
) {
    val brush = remember(key1 = avatarRes) {
        ShaderBrush(
            BitmapShader(
                ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
                Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT
            )
        )
    }

    Box(
        modifier = modifier.background(brush)
    ) {
        /* ... */
    }
}

Sonraki snippet'te state, basit bir durum tutucu sınıfına MyAppState çekilir. remember kullanarak sınıfın bir örneğini başlatmak için bir rememberMyAppState işlevi sunar. Yeniden bestelemelerden etkilenmeyen bir örnek oluşturmak için bu tür işlevleri sunmak, Compose'da yaygın olarak kullanılan bir kalıptır. rememberMyAppState işlevi, remember için key parametresi olarak işlev gören windowSizeClass değerini alır. Bu parametre değişirse uygulamanın, düz durum tutucu sınıfını en son değerle yeniden oluşturması gerekir. Bu durum, örneğin kullanıcı cihazı döndürürse gerçekleşebilir.

@Composable
private fun rememberMyAppState(
    windowSizeClass: WindowSizeClass
): MyAppState {
    return remember(windowSizeClass) {
        MyAppState(windowSizeClass)
    }
}

@Stable
class MyAppState(
    private val windowSizeClass: WindowSizeClass
) { /* ... */ }

Oluşturma, bir anahtarın değişip değişmediğine karar vermek ve depolanan değeri geçersiz kılmak için sınıfın eşit uygulamasını kullanır.

Yeniden oluşturmanın ötesinde anahtarlar içeren mağaza durumu

rememberSaveable API'si, verileri Bundle içinde saklayabilen remember sarmalayıcısıdır. Bu API, durumun yalnızca yeniden derlemeden değil, etkinlik yeniden oluşturma ve sistem tarafından başlatılan işlem sonlandırma işlemlerinden de sağ çıkmasına olanak tanır. rememberSaveable, remember'nin keys almasıyla aynı amaçla input parametrelerini alır. Girişlerden herhangi biri değiştiğinde önbellek geçersiz kılınır. İşlev yeniden oluşturulduktan sonra rememberSaveable, hesaplama lambda bloğunu yeniden yürütür.

Aşağıdaki örnekte, rememberSaveable, typedQuery değişene kadar userTypedQuery değerini saklar:

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

Daha fazla bilgi

Durum ve Jetpack Compose hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın.

Örnekler

Codelab uygulamaları

Videolar

Bloglar