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ı, kullanıcıya durumu gösterir. Android uygulamalarında durumla ilgili birkaç örnek:
- Ağ bağlantısı kurulamadığında gösterilen bir Snackbar.
- 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, bir Android uygulamasında durumu nerede ve nasıl depolayıp kullanacağınızı açıkça belirtmenize yardımcı olur. Bu kılavuzda, durum ile composable'lar arasındaki bağlantı ve Jetpack Compose'un durumla çalışmak için sunduğu API'ler ele alınmaktadır.
Durum ve oluşturma
Compose, bildirim temelli bir araçtır. Bu nedenle, 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ü durumunun temsilleridir. Bir durum her güncellendiğinde yeniden oluşturma işlemi gerçekleşir. Bu nedenle, TextField gibi öğeler, zorunlu XML tabanlı görünümlerde olduğu gibi otomatik olarak güncellenmez. Bir composable'ın buna göre güncellenmesi 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 işlevinin kendisini güncellememesi, value parametresi değiştiğinde güncellenmesidir. Bunun nedeni, Compose'da oluşturma ve yeniden oluşturma işlemlerinin işleyiş şeklidir.
İlk oluşturma: Oluşturulabilir öğelerin ilk kez çalıştırılmasıyla bir oluşturma işleminin yapılması.
Yeniden oluşturma: Veriler değiştiğinde oluşturmayı güncellemek için composable'ları yeniden çalıştırma.
İlk oluşturma ve yeniden oluşturma hakkında daha fazla bilgi edinmek için Compose'da düşünme başlıklı makaleyi inceleyin.
Durum (state) ve composable'lar
Oluşturulabilir işlevler, bir nesneyi bellekte depolamak için remember API'sini kullanabilir. remember tarafından hesaplanan bir değer, ilk oluşturma sırasında oluşturmaya kaydedilir ve yeniden oluşturma sırasında kaydedilen değer döndürülür. remember, hem değiştirilebilir hem de değiştirilemez nesneleri depolamak için kullanılabilir.
mutableStateOf, Compose çalışma zamanıyla entegre edilmiş gözlemlenebilir bir tür olan MutableState<T> gözlemlenebilirini oluşturur.
interface MutableState<T> : State<T> {
override var value: T
}
value üzerinde yapılan değişiklikler, value değerini okuyan tüm 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 bildirimler eşdeğerdir ve durumun farklı kullanımları için söz dizimi kolaylığı olarak sağlanır. Yazdığınız composable'da en kolay okunabilen kodu üreten yöntemi seçmeniz gerekir.
by temsilci 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 kullanabilir, hatta hangi composable'ların gösterileceğini değiştirmek için ifadelerde mantık olarak kullanabilirsiniz. Örneğin, ad boşsa karşılama mesajını göstermek 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çinde kaydedilebilecek tüm değerleri otomatik olarak kaydeder. Diğer değerler için özel bir kaydedici nesnesi iletebilirsiniz.
Desteklenen diğer durum türleri
Compose, durumu tutmak 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 composable'ların otomatik olarak yeniden oluşturulabilmesi için bu türü State<T> türüne dönüştürmeniz gerekir.
Compose, Android uygulamalarında kullanılan yaygın gözlemlenebilir türlerden State<T> oluşturma işlevleriyle birlikte gelir. Bu entegrasyonları kullanmadan önce uygun yapıları aşağıdaki gibi ekleyin:
Flow:collectAsStateWithLifecycle()collectAsStateWithLifecycle(), yaşam döngüsüne duyarlı bir şekildeFlow'dan değerleri toplayarak uygulamanızın uygulama kaynaklarını korumasına olanak tanır. ComposeState'dan yayılan en son değeri temsil eder. Android uygulamalarında akışları toplamak için önerilen yöntem olarak bu API'yi kullanın.build.gradledosyasında aşağıdaki bağımlılık gereklidir (2.6.0-beta01 veya daha yeni bir sürüm olmalıdır):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.11.0")
}
Modern
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.11.0"
}
-
collectAsState,collectAsStateWithLifecycle'e benzer. Bu işlev deFlow'den değerleri toplar ve bunları ComposeState'e dönüştürür.Yalnızca Android'e özel olan
collectAsStateWithLifecycleyerine platformdan bağımsız kod içincollectAsStatekullanın.collectAsState,compose-runtimeiçinde mevcut olduğundan ek bağımlılıklar gerekli değildir. -
observeAsState(), buLiveDataöğesini gözlemlemeye başlar ve değerleriniStatekullanarak temsil eder.build.gradledosyasında aşağıdaki bağımlılık gereklidir:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.12.1")
}
Modern
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.12.1"
}
-
subscribeAsState(), RxJava2'nin reaktif akışlarını (ör.Single,Observable,Completable) ComposeStateöğesine dönüştüren uzantı işlevleridir.build.gradledosyasında aşağıdaki bağımlılık gereklidir:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.12.1")
}
Modern
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.12.1"
}
-
subscribeAsState(), RxJava3'ün reaktif akışlarını (ör.Single,Observable,Completable) ComposeState'e dönüştüren uzantı işlevleridir.build.gradledosyasında aşağıdaki bağımlılık gereklidir:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.12.1")
}
Modern
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.12.1"
}
Durumlu ve durumsuz karşılaştırması
Bir nesneyi depolamak için remember kullanan bir composable, dahili durum oluşturarak composable'ı durum bilgili hale getirir. HelloContent, name durumunu dahili olarak tuttuğu ve değiştirdiği için durum bilgili bir composable örneğidir. Bu, arayanın durumu kontrol etmesi gerekmediği ve durumu kendisi yönetmek zorunda kalmadan kullanabileceği durumlarda yararlı olabilir. Ancak dahili duruma sahip composable'lar daha az yeniden kullanılabilir ve test edilmesi daha zor olur.
Durumsuz bir composable, herhangi bir durum içermeyen bir composable'dır. Durumsuzluk elde etmenin yaygın bir yolu, state hoisting kullanmaktır.
Yeniden kullanılabilir composable'lar geliştirirken genellikle aynı composable'ın hem durum bilgisi olan hem de durum bilgisi olmayan bir sürümünü kullanıma sunmak istersiniz. Durum bilgisi olan sürüm, durum bilgisiyle ilgilenmeyen çağrıcılar için uygundur. Durum bilgisi olmayan sürüm ise durum bilgisini kontrol etmesi veya yükseltmesi gereken çağrıcılar için gereklidir.
State hoisting
Compose'da state hoisting, bir composable'ı durumsuz hale getirmek için durumu composable'ın çağrıcısına taşıma kalıbıdır. Jetpack Compose'da state hoisting için genel kalıp, durum değişkenini iki parametreyle değiştirmektir:
value: T: gösterilecek mevcut değeronValueChange: (T) -> Unit: değerin değiştirilmesini isteyen bir etkinlik. BuradaT, önerilen yeni değerdir.
Ancak onValueChange ile sınırlı değilsiniz. Composable için daha spesifik etkinlikler uygunsa bunları lambda kullanarak tanımlamanız gerekir.
Bu şekilde yukarı çekilen durumun bazı önemli özellikleri vardır:
- Tek doğruluk kaynağı: Durumu kopyalamak yerine taşıyarak tek bir doğruluk kaynağı olmasını sağlıyoruz. Bu sayede hatalar önlenir.
- Kapsüllenmiş: Yalnızca durum bilgisi olan composable'lar durumlarını değiştirebilir. Tamamen dahili bir süreçtir.
- Paylaşılabilir: Yükseltilmiş durum, birden fazla composable ile paylaşılabilir.
namedeğerini farklı bir composable'da okumak isterseniz hoisting sayesinde bunu yapabilirsiniz. - Kesilebilir: Durum bilgisi olmayan bileşenleri çağıranlar, durumu değiştirmeden önce etkinlikleri yoksaymaya veya değiştirmeye karar verebilir.
- Bağımsız: Durum bilgisi olmayan composable'ların durumu herhangi bir yerde depolanabilir. Örneğin, artık
nameöğesiniViewModeliçine taşıyabilirsiniz.
Örnekte, name ve onValueChange değerlerini HelloContent öğesinden çıkarıp ağaçta yukarı taşıyarak HelloContent öğesini çağıran 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") }) } }
Durumu HelloContent öğesinden çıkararak composable'ı daha kolay anlayabilir, farklı durumlarda yeniden kullanabilir ve test edebilirsiniz. HelloContent, durumunun depolanma
şeklinden bağımsızdır. Ayrıştırma, üzerinde değişiklik yaptığınızda veya HelloScreen
'i değiştirdiğinizde HelloContent'in uygulanma şeklini değiştirmeniz gerekmediği anlamına gelir.
Durumun aşağı, etkinliklerin yukarı gittiği bu 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ı takip ederek kullanıcı arayüzünde durumu gösteren composable'ları, uygulamanızın durumu depolayan ve değiştiren bölümlerinden ayırabilirsiniz.
- Durum, durumu kullanan (okuyan) tüm composable'ların en az en düşük ortak üst öğesine yükseltilmelidir.
- Durum, değiştirilebileceği en yüksek seviyeye (yazma) kadar yukarı taşınmalıdır.
- İki durum aynı etkinliklere yanıt olarak değişiyorsa birlikte yukarı çekilmeleri gerekir.
Durumu bu kuralların gerektirdiğinden daha yukarı taşıyabilirsiniz ancak durumu daha aşağı taşımak, tek yönlü veri akışını takip etmeyi zorlaştırır veya imkansız hale getirir.
Daha fazla bilgi için Durumun nerede yukarı çekileceği sayfasını inceleyin.
Compose'da durumu geri yükleme
rememberSaveable API'si, yeniden oluşturmaların yanı sıra kaydedilen örnek durumu mekanizmasını kullanarak etkinlik veya süreç yeniden oluşturma işlemlerinde de durumu koruduğu için remember ile benzer şekilde davranır. Örneğin, ekran döndürüldüğünde
bu durum yaşanır.
Durumu depolama yöntemleri
Bundle'e eklenen tüm veri türleri otomatik olarak kaydedilir. Bundle uygulamasına eklenemeyen bir öğeyi kaydetmek istiyorsanız birkaç seçeneğiniz vardır.
Parcelize
En basit çözüm, nesneye @Parcelize ek açıklamasını eklemektir. Nesne, parcelable olur ve paketlenebilir. Örneğin, bu kod, parcelable 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")) } }
MapSaver
@Parcelize herhangi bir nedenle uygun değilse bir nesneyi sistemin Bundle içine kaydedebileceği bir değer kümesine 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")) } }
ListSaver
Harita için anahtarları tanımlama ihtiyacını ortadan kaldırmak amacıyla listSaver 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")) } }
Compose'da durum bilgisi depolayıcılar
Basit state hoisting, composable işlevlerin kendisinde yönetilebilir. Ancak takip edilecek durum bilgisi miktarı artarsa veya composable işlevlerde gerçekleştirilecek mantık ortaya çıkarsa mantık ve durum bilgisi sorumluluklarını diğer sınıflara (durum bilgisi depolayıcılar) devretmek iyi bir uygulamadır.
Diğer materyallerde durum bilgisi depolayıcıların hoisted state nesneleri olarak da adlandırıldığını unutmayın.
Daha fazla bilgi edinmek için Compose'da state hoisting belgelerine veya daha genel olarak mimari kılavuzundaki Durum bilgisi depolayıcılar ve kullanıcı arayüzü durumu sayfasına bakın.
Anahtarlar değiştiğinde hatırlama hesaplamalarını yeniden tetikleme
remember API, genellikle MutableState ile birlikte kullanılır:
var name by remember { mutableStateOf("") }
Burada remember işlevinin kullanılması, MutableState değerinin yeniden oluşturma işlemlerinden etkilenmemesini sağlar.
Genel olarak remember, bir calculation lambda parametresi alır. remember ilk kez çalıştırıldığında calculation lambda işlevini ç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, başlatılması veya hesaplanması maliyetli olan herhangi bir nesneyi ya da işlem sonucunu oluşturmada depolamak için de remember kullanabilirsiniz. Bu hesaplamayı her yeniden oluşturma işleminde tekrarlamak istemeyebilirsiniz.
Örneğin, aşağıdaki ShaderBrush nesnesini oluşturmak maliyetli bir işlemdir:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember, oluşturmadan ayrılana kadar değeri saklar. Ancak önbelleğe alınan değeri geçersiz kılmanın bir yolu vardır. remember API'si ayrıca bir key veya keys parametresi alır. Bu anahtarlardan herhangi biri değişirse işlev bir sonraki yeniden oluşturma işleminde remember önbelleği geçersiz kılar ve hesaplama lambda bloğunu tekrar yürütür. Bu mekanizma, oluşturmadaki bir nesnenin kullanım ömrü üzerinde kontrol sahibi olmanızı sağlar. Hesaplama, hatırlanan değer oluşturmadan ayrılana kadar değil, girişler değişene kadar geçerli kalır.
Aşağıdaki örneklerde bu mekanizmanın işleyiş şekli gösterilmektedir.
Bu snippet'te bir ShaderBrush oluşturulur ve Box composable'ın arka plan boyası olarak kullanılır. remember, daha önce açıklandığı gibi yeniden oluşturulması maliyetli olduğu için ShaderBrush örneğini depolar. remember, key1 parametresi olarak avatarRes değerini alır. Bu, seçilen arka plan resmidir. 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 olarak 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) ) { /* ... */ } }
Aşağıdaki snippet'te durum, düz durum bilgisi depolayıcı sınıfına
MyAppState taşınır. remember kullanarak sınıfın bir örneğini başlatmak için rememberMyAppState işlevini kullanıma sunar. Yeniden oluşturma işlemlerinden etkilenmeyen bir örnek oluşturmak için bu tür işlevleri kullanmak, Compose'da yaygın bir kalıptır. rememberMyAppState işlevi, remember için key parametresi olarak kullanılan windowSizeClass değerini alır. Bu parametre değişirse uygulamanın, düz durum bilgisi depolayıcı sınıfını en son değerle yeniden oluşturması gerekir. Örneğin, kullanıcı cihazı döndürdüğünde bu durum yaşanabilir.
@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 equals uygulamasını kullanır.
Yeniden oluşturmanın ötesinde anahtarlarla durumu depolama
rememberSaveable API, verileri Bundle içinde depolayabilen remember için bir sarmalayıcıdır. Bu API, durumun yalnızca yeniden oluşturma işleminden değil, aynı zamanda etkinliğin yeniden oluşturulmasından ve sistem tarafından başlatılan işlem sonlandırmadan da etkilenmemesini sağlar.
rememberSaveable, input
remember ile aynı amaçla keys parametrelerini alır. Girişlerden herhangi biri değiştiğinde önbellek geçersiz kılınır. İşlev bir sonraki yeniden oluşturma işleminde rememberSaveable, hesaplama lambda bloğunu yeniden yürütür.
Aşağıdaki örnekte, rememberSaveable, typedQuery değişene kadar userTypedQuery değerini depolar:
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 kaynakları inceleyin.
Örnekler
Codelab uygulamaları
Videolar
Bloglar
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Compose kullanıcı arayüzünüzü tasarlama
- Compose'da kullanıcı arayüzü durumunu kaydetme
- Compose'da yan etkiler