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 oda veritabanından sınıftaki bir değişkene kadar her şeyi kapsar.

Tüm Android uygulamaları, durumu kullanıcıya gösterir. Aşağıda, Android uygulamalarındaki durum ile ilgili birkaç örnek verilmiştir:

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

Jetpack Compose, bir Android uygulamasında durumu nerede ve nasıl depoladığınızı ve kullandığınızı açıkça belirtmenize yardımcı olur. Bu kılavuz, durum ile composable'lar arasındaki bağlantıya ve Jetpack Compose'un durumlarla daha kolay çalışmayı sunduğu API'lere odaklanmaktadır.

Durum ve bileşim

Oluşturma bildirim temellidir. Bu nedenle, aynı composable'ı yeni bağımsız değişkenlerle çağırarak güncellemenin tek yolu budur. Bu bağımsız değişkenler, kullanıcı arayüzü durumunu temsil eder. Bir eyalet her güncellendiğinde yeniden oluşturma işlemi gerçekleşir. Sonuç olarak TextField gibi öğeler, zorunlu XML tabanlı görünümlerde olduğu gibi otomatik olarak güncellenmez. composable'ın uygun şekilde güncellenmesi için yeni durumu açıkça bildirilmelidir.

@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 komutu çalıştırıp metin girmeye çalışırsanız hiçbir şey olmadığını görürsünüz. Çünkü TextField kendini güncellemez. value parametresi değiştiğinde güncellenir. Bu durum, kompozisyon ve yeniden kompozisyonun Oluşturma'daki işleyiş şeklinden kaynaklanır.

İlk beste ve yeniden kompozisyon hakkında daha fazla bilgi edinmek için Yazmada Düşünme bölümüne bakın.

Composable'lar içindeki durum

Oluşturulabilir işlevler, bir nesneyi bellekte depolamak için remember API'yi kullanabilir. remember tarafından hesaplanan bir değer, ilk beste sırasında Beste içinde depolanır ve depolanan değer, yeniden oluşturma sırasında döndürülür. remember hem değişken hem de sabit nesneleri depolamak için kullanılabilir.

mutableStateOf, oluşturma çalışma zamanıyla entegre edilmiş gözlemlenebilir bir tür olan MutableState<T> adlı gözlemlenebilir bir tür oluşturur.

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

value üzerinde yapılan değişiklikler, value değerini okuyan composable işlevlerin yeniden oluşturulmasını planlar.

Bir composable'da 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 beyanlar eşdeğerdir ve durumun farklı kullanımları için söz dizimi şekeri olarak sağlanır. Yazdığınız composable'da en kolay okunan kodu üreten kodu seçmeniz gerekir.

by yetki 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 bir 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ın gösterilmesini istemiyorsanız if ifadesindeki durumu 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şturmalarda durumu korumanıza yardımcı olur ancak yapılandırma değişiklikleri genelinde 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 tasarruf nesnesi ekleyebilirsiniz.

Desteklenen diğer durum türleri

Oluşturma işlemi, durumu muhafaza etmek 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 composable'ların durum değiştiğinde otomatik olarak yeniden oluşturabilmesi için bunu State<T> biçimine dönüştürmeniz gerekir.

Android uygulamalarında yaygın olarak kullanılan gözlemlenebilir türlerden State<T> oluşturmanıza olanak tanıyan işlevlerle içerik gönderin. Bu entegrasyonları kullanmadan önce aşağıda belirtildiği gibi uygun yapıları ekleyin:

  • Flow: collectAsStateWithLifecycle()

    collectAsStateWithLifecycle(), Flow'teki değerleri yaşam döngüsüne duyarlı bir şekilde toplayarak uygulamanızın uygulama kaynaklarını korumasına olanak tanır. Oluşturma State öğesinden yayınlanan en son değeri temsil eder. Android uygulamalarında akışları toplamak için önerilen yöntem olarak bu API'yi kullanın.

    build.gradle dosyasında aşağıdaki dependency (bağımlılık) gereklidir (2.6.0-beta01 veya daha yeni olması gerekir):

Kotlin

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

Modern

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

    collectAsState, Flow öğesinden değer toplayıp Oluştur'a State dönüştürdüğü için collectAsStateWithLifecycle ile benzerdir.

    Platformdan bağımsız kod için yalnızca Android'de bulunan collectAsStateWithLifecycle yerine collectAsState kullanın.

    compose-runtime bölgesinde kullanılabildiğinden collectAsState için ek bağımlılıklara gerek yoktur.

  • LiveData: observeAsState()

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

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

Kotlin

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

Modern

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

Kotlin

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

Modern

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

Kotlin

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

Modern

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

Durum bilgili ve durum bilgisiz

Bir nesneyi depolamak için remember kullanan bir composable, dahili durum oluşturarak composable'ı durum bilgili hale getirir. HelloContent, name durumunu içeride tutup değiştirdiği için durum bilgili composable'a örnektir. Bu özellik, arayan kişinin durumu kontrol etmesi gerekmediği ve kendi başına yönetmek zorunda kalmadan kullanabileceği durumlarda yararlı olabilir. Bununla birlikte, dahili durumu olan composable'ların tekrar kullanılabilirlik durumu daha azdır ve test edilmesi daha zordur.

Durum bilgisiz composable, herhangi bir durum içermeyen composable'dır. Durum bilgisizliği sağlamanın kolay bir yolu da eyalet kaldırma özelliğini kullanmaktır.

Yeniden kullanılabilir composable'lar geliştirirken genellikle aynı composable'ın hem durum bilgili hem de durum bilgisiz sürümünü sunmak istersiniz. Durum bilgili sürüm, durumla ilgilenmeyen arayanlar için kullanışlıdır. Durum bilgisiz sürüm ise, eyaleti kontrol etmesi veya kaldırması gereken arayanlar için gereklidir.

Yükseliş

Compose'da durum yükseltme, composable'ı durum bilgisiz hale getirmek için bir composable'ın arayanına durum taşıma kalıbıdır. Jetpack Compose'da durum yükseltme için genel kalıp, durum değişkenini iki parametreyle değiştirmektir:

  • value: T: görüntülenecek geçerli değer
  • onValueChange: (T) -> Unit: Değerin değiştirilmesini isteyen bir etkinliktir. Burada T, önerilen yeni değerdir

Ancak, bu hizmet onValueChange ile sınırlı değildir. composable için daha belirli etkinlikler uygunsa lambdas kullanarak tanımlamalısınız.

Bu şekilde kaldırılan eyaletin bazı önemli özellikleri vardır:

  • Tek doğru kaynağı: Durumu kopyalamak yerine taşıyarak tek bir doğru kaynak olmasını sağlarız. Bu, hataları önlemenize yardımcı olur.
  • Kapsüllü: Yalnızca durum bilgili composable'lar durumlarını değiştirebilir. Tamamen dahili.
  • Paylaşılabilir: Kaldırılmış durumu birden fazla composable ile paylaşılabilir. name öğesini farklı bir composable'da okumak isterseniz kaldırma özelliği bunu yapmanıza olanak tanır.
  • Anlaşılabilir: Durum bilgisiz composable'ları çağıran kişiler, 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 name öğesini ViewModel içine taşımak mümkündür.

Örnekte, name ve onValueChange öğelerini HelloContent konumundan çıkarıp ağacın yukarısına, HelloContent adlı bir HelloScreen composable'ına 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") })
    }
}

Eyalet HelloContent dışına çıkarıldığında composable hakkında akıl yürütme, onu farklı durumlarda yeniden kullanmak ve test etmek daha kolay olur. HelloContent, durumunun depolanma şeklinden ayrıştırılır. Ayrıştırma, HelloScreen değerini değiştirir veya değiştirirseniz HelloContent öğesinin uygulanma şeklini değiştirmenize gerek olmadığı anlamına gelir.

Durumun düştüğü, etkinliklerin yükseldiği kalıpa tek yönlü veri akışı denir. Bu durumda, eyalet HelloScreen değerinden HelloContent değerine, etkinlikler ise HelloContent değerinden HelloScreen değerine yükselir. Tek yönlü veri akışını gerçekleştirerek kullanıcı arayüzünde durumu görüntüleyen composable'ları, uygulamanızın durumunu depolayan ve değiştiren bölümlerinden ayırabilirsiniz.

Daha fazla bilgi edinmek için Eyalet nereden kaldırılır? sayfasına bakın.

Oluşturma'da durum geri yükleniyor

rememberSaveable API, yeniden oluşturmalarda ve kaydedilen örnek durum mekanizmasını kullanarak etkinlik veya süreç yeniden oluşturma genelinde durumu koruduğu için remember ile benzer şekilde davranır. Örneğin, ekran döndürüldüğünde bu durum gerçekleşir.

Durumu depolamanın yolları

Bundle öğesine eklenen tüm veri türleri otomatik olarak kaydedilir. Bundle içine eklenemeyen bir öğeyi kaydetmek istiyorsanız yararlanabileceğiniz çeşitli seçenekler vardır.

Parselleştir

En basit çözüm, nesneye @Parcelize ek açıklamasını eklemektir. Nesne ayrıştırılabilir hale gelir ve paketlenebilir. Örneğin, bu kod ayrıştırılabilir bir City veri türü oluşturur ve duruma kaydeder.

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

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

Harita Tasarruf Aracı

Herhangi bir nedenle @Parcelize uygun değilse bir nesneyi sistemin Bundle konumuna kaydedebileceği bir değer grubuna dönüştürmek için kendi kuralınızı tanımlamak üzere 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"))
    }
}

Liste Kaydeden

Harita için anahtarları tanımlamanızı önlemek amacıyla listSaver ve dizinlerini anahtar olarak da 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"))
    }
}

Compose'da eyalet sahipleri

Basit durum yükseltme, composable işlevlerden yönetilebilir. Ancak artışları izlemek için gereken durum miktarı veya composable işlevlerde uygulama mantığı ortaya çıkarsa, mantık ve eyalet sorumluluklarını diğer sınıflara (eyalet sahiplerine) devretmek iyi bir uygulamadır.

Daha fazla bilgi edinmek için Compose'da eyalet yükseltme belgelerine veya daha genel olarak mimari kılavuzundaki Eyalet sahipleri ve Kullanıcı Arayüzü Durumu sayfasına bakın.

Yeniden tetikleyici, anahtarlar değiştiğinde hesaplamaları hatırlar

remember API, sık sık MutableState ile birlikte kullanılır:

var name by remember { mutableStateOf("") }

Burada, remember işlevi kullanıldığında MutableState değeri yeniden oluşturmalardan sonra da dayanabilir.

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

Önbelleğe alma durumunun yanı sıra, Beste'deki ilk kullanıma hazırlama veya hesaplama işlemi pahalı olan herhangi bir nesneyi veya işlemin sonucunu depolamak için remember kullanabilirsiniz. Bu hesaplamayı her yeniden oluşturmada tekrarlamak istemeyebilirsiniz. Örnek olarak, pahalı bir işlem olan bu ShaderBrush nesnesinin oluşturulması verilebilir:

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

remember, Beste'den ayrılana kadar değeri saklar. Bununla birlikte, önbelleğe alınan değeri geçersiz kılmanın bir yolu vardır. remember API, key veya keys parametresini de alır. Bu anahtarlardan herhangi biri değişirse işlevin bir sonraki yeniden derlemesinde remember önbelleği geçersiz kılar ve hesaplama lambda blokunu tekrar yürütür. Bu mekanizma, Beste'deki bir nesnenin yaşam süresi üzerinde kontrol sahibi olmanızı sağlar. Hesaplama, hatırlanan değer Beste'den ayrılana kadar değil, girişler değişene kadar geçerli kalır.

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

Bu snippet'te ShaderBrush oluşturulur ve Box composable'ının arka plan boyası olarak kullanılır. remember, daha önce açıklandığı gibi yeniden oluşturmak pahalı olduğu için ShaderBrush örneğini depolar. remember, avatarRes parametresini key1 parametresi olarak alır. Bu, seçili arka plan resmidir. avatarRes değişirse fırça, yeni resimle yeniden derlenir ve Box öğesine yeniden uygulanır. Bu durum, kullanıcı bir seçiciden arka plan olarak başka bir resim seçtiğinde meydana gelebilir.

@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)
    ) {
        /* ... */
    }
}

Bir sonraki snippet'te eyalet, düz durum sahibi sınıfına MyAppState çekilir. Sınıfın bir örneğini remember kullanarak başlatmak için bir rememberMyAppState işlevi sunar. Bu tür işlevlerin, yeniden derlemelerden sonra başarılı olan bir örnek oluşturmak için kullanıma sunulması, Compose'da yaygın 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 oluşabilir.

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

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

Compose, 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şittir uygulamasını kullanır.

Durumu, yeniden düzenlemenin ötesinde anahtarlarla depolayın

rememberSaveable API, verileri Bundle içinde depolayabilen, remember etrafındaki bir sarmalayıcıdır. Bu API, eyaletin yalnızca yeniden oluşturma işleminden değil, aynı zamanda etkinlik yeniden oluşturma ve sistem tarafından başlatılan işlem ölümlerinden de kurtulmasına olanak tanır. rememberSaveable, remember hizmetinin keys aldığı amaçla input parametre alır. Girişlerden herhangi biri değiştiğinde önbellek geçersiz kılınır. İşlev yeniden derlendiğinde rememberSaveable, hesaplama lambda bloğunu yeniden yürütür.

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

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

Daha fazla bilgi

State ve Jetpack Compose hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara başvurun.

Numuneler

Codelab uygulamaları

Videolar

Bloglar