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

Tüm Android uygulamaları, kullanıcıya gösterilir. Android uygulamalarında birkaç durum örneği:

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

Jetpack Compose, bir Android uygulamasındaki durumu nerede ve nasıl depoladığınızı ve kullandığınız konusunda açık olmanıza yardımcı olur. Bu kılavuz, durum ile composable'lar arasındaki bağlantıya ve Jetpack Compose'un durum üzerinde daha kolay çalışmak için sunduğu API'lere odaklanmaktadır.

Durum ve yapı

Compose bildirim temellidir. Dolayısıyla, bunu güncellemenin tek yolu aynı composable'ı 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 bir 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 composable'ın uygun şekilde güncellenmesi için yeni durumunun açıkça bildirilmesi 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 komutu ç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, bestenin ve yeniden bestenin Compose’da çalışma şeklidir.

İlk beste ve yeniden düzenleme hakkında daha fazla bilgi edinmek için İçerik Oluşturmada Düşünme sayfasına bakın.

composable'larda 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 Bileşim'de 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, yazma çalışma zamanıyla entegre gözlemlenebilir bir tür olan gözlemlenebilir bir MutableState<T> türü oluşturur.

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

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

Bir composable'da MutableState nesnesini 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 farklı devlet kullanımları için söz dizimi şekeri olarak sunulur. Yazdığınız composable içinde okunması en kolay kodu üreten kodu seçmelisiniz.

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ş olduğunda karşılama mesajının gösterilmesini istemiyorsanız if ifadesinde 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 bir özel koruyucu nesnesi aktarabilirsiniz.

Desteklenen diğer eyalet türleri

Compose, durum bilgisini korumak için MutableState<T> kullanmanızı gerektirmez. Diğer gözlemlenebilir türleri destekler. Composer'da başka bir gözlemlenebilir türü okumadan önce, durum değiştiğinde composable'ların otomatik olarak yeniden derlenebilmesi için bunu bir State<T> biçimine 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 gemiler oluşturun. Bu entegrasyonları kullanmadan önce aşağıda açıklandığı şekilde 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ş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ık zorunludur (2.6.0-beta01 veya daha yeni bir sürüm olmalıdır):

Kotlin

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

Eski

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

    collectAsState, aynı zamanda bir Flow öğesinden değerleri toplayıp Oluştur simgesine State dönüştürdüğü için collectAsStateWithLifecycle ile benzerdir.

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

    collectAsState, compose-runtime üzerinde kullanılabildiğinden ek bağımlılıklar gerekli değildir.

  • LiveData: observeAsState()

    observeAsState(), bu LiveData öğesini gözlemlemeye başlar ve değerlerini State üzerinden temsil eder.

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

Kotlin

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

Eski

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

Kotlin

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

Eski

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

Kotlin

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

Eski

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

Durum bilgili ve durum bilgisiz

Nesneyi depolamak için remember kullanan bir composable, dahili durum oluşturarak composable'ı durum bilgili hale getirir. HelloContent, name durumunu dahili olarak muhafaza edip değiştirdiği için durum bilgili composable'lara örnektir. Bu, çağrıyı yapanın durumu kontrol etmesinin gerekmediği ve devleti yönetmek zorunda kalmadan bunu kullanabildiği durumlarda yararlı olabilir. Ancak, dahili duruma sahip composable'lar daha az yeniden kullanılabilir ve test edilmesi daha zordur.

Durum bilgisiz composable, herhangi bir durumda olmayan bir composable'dır. Durum bilgisiz duruma ulaşmanın kolay bir yolu, durum kaldırma kullanmaktır.

Yeniden kullanılabilir composable'lar geliştirirken, genellikle aynı composable'ın hem durum bilgili hem de durum bilgisiz sürümünü göstermek istersiniz. Durum bilgili sürüm, devletle ilgilenmeyen arayanlar için uygundur ve durum bilgisiz sürüm, durumu kontrol etmesi veya kaldırması gereken arayanlar için gereklidir.

Eyalet 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ırmanın genel kalıbı 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 kullanabileceğiniz seçenekler onValueChange ile sınırlı değildir. composable için daha belirli etkinlikler uygunsa bunları lambdas kullanarak tanımlamanız gerekir.

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

  • Tek doğru kaynak: Durumu kopyalamak yerine hareket ettirerek tek bir doğru kaynağın bulunmasını sağlıyoruz. Bu sayede hatalardan kaçınabilirsiniz.
  • Encapsulated: Yalnızca durum bilgili composable'ların durumlarını değiştirebilir. Her şey tamamen dahili.
  • Paylaşılabilir: Kaldırılmış durum, birden fazla composable ile paylaşılabilir. name öğesini farklı bir composable'da okumak isterseniz kaldırma işlemi bunu yapmanıza olanak tanır.
  • Kesişilebilir: Durum bilgisiz composable'lara çağrı yapan 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, name artık bir ViewModel içine taşınabilir.

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

HelloContent durumunu kaldırarak composable hakkında akıl yürütmek, bunu farklı durumlarda yeniden kullanmak ve test etmek daha kolay olur. 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 düştüğü ve etkinliklerin arttığı kalıp tek yönlü veri akışı olarak adlandırılır. Bu durumda, durum HelloScreen değerinden HelloContent değerine iner ve etkinlikler HelloContent değerinden HelloScreen değerine çıkar. Tek yönlü veri akışını izleyerek kullanıcı arayüzünde durum gösteren composable'ları, uygulamanızın durumu depolayan ve değiştiren bölümlerinden ayırabilirsiniz.

Daha fazla bilgi edinmek için Kaldırma durumu sayfasını inceleyin.

Compose'da durum geri yükleniyor

rememberSaveable API, remember gibi davranır çünkü yeniden oluşturmalarda ve kaydedilen örnek durumu mekanizmasını kullanarak etkinlik veya işlem oluşturma sırasında durumu korur. Örneğin bu durum, ekran döndürüldüğünde ortaya çıkar.

Eyalet bilgisini depolamanın yolları

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

Paket haline getir

En basit çözüm, nesneye @Parcelize notunu 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 bunu duruma kaydeder.

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

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

Harita Koruyucu

Herhangi bir nedenle @Parcelize uygun değilse bir nesneyi Bundle için kaydedebileceğiniz bir değer grubuna dönüştürmek amacıyla 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"))
    }
}

Liste Kaydedeni

Harita için anahtarları tanımlama ihtiyacını ortadan kaldırmak için 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 kaldırma işlemi, composable işlevlerin kendisinden yönetilebilir. Bununla birlikte, artışların takip edilmesi gereken durum miktarı veya composable işlevlerde gerçekleştirme mantığı varsa bu mantık ve beyan sorumluluklarını diğer sınıflara, yani devlet sahiplerine delege etmek iyi bir uygulamadır.

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

Tuşlar değiştiğinde hesaplamaları hatırlamayı yeniden tetikle

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

var name by remember { mutableStateOf("") }

Burada remember işlevinin kullanılması, MutableState değerinin yeniden bestelerde hayatta kalmasını sağlar.

remember genellikle calculation lambda parametresi alır. remember ilk kez ç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 remember uygulamasını, herhangi bir nesneyi veya bestedeki başlatılması ya da hesaplanması pahalı olan bir işlemin sonucunu depolamak için de kullanabilirsiniz. Bu hesaplamayı her yeniden oluşturmada 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 Beste'den ayrılana kadar depolar. Bununla birlikte, önbelleğe alınan değeri geçersiz kılmanın bir yolu vardır. remember API ayrıca bir key veya keys parametresi alır. Bu anahtarlardan herhangi biri değişirse işlev yeniden oluşturulduğunda, remember önbelleği geçersiz kılar ve hesaplamayı tekrar lambda bloku yürütür. Bu mekanizma, Bestedeki bir nesnenin ömrü boyunca 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 örneklerde bu mekanizmanın nasıl çalıştığı gösterilmektedir.

Bu snippet'te ShaderBrush oluşturulup Box bir composable'ın arka plan boyası olarak kullanılır. remember, daha önce açıklandığı gibi yeniden oluşturulması pahalı olduğundan ShaderBrush örneğini depolar. 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 öğesine 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 durum düz 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 görevi gören windowSizeClass değerini alır. Bu parametre değişirse uygulamanın en son değerle düz durum tutucu sınıfını 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
) { /* ... */ }

Compose, bir anahtarın değişip değişmediğine ve depolanan değeri geçersiz kılıp kılmadığına karar vermek için sınıfın eşittir uygulamasını kullanır.

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

rememberSaveable API, verileri Bundle içinde depolayabilen, remember etrafında çalışan bir sarmalayıcıdır. Bu API, devletin yalnızca yeniden oluşturma değil, aynı zamanda etkinlik yeniden oluşturma ve sistem tarafından başlatılan süreç ölümünü de atlatmasını sağlar. rememberSaveable, remember ile keys aynı amaçla input parametre 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şikliklerine kadar userTypedQuery depolar:

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

Daha fazla bilgi

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

Numuneler

Codelab'ler

Videolar

Bloglar