Compose'da durum ömürleri

Jetpack Compose'da composable işlevler genellikle remember işlevini kullanarak durumu korur. Hatırlanan değerler, Durum ve Jetpack Compose bölümünde açıklandığı gibi yeniden oluşturma işlemlerinde tekrar kullanılabilir.

remember, değerleri yeniden oluşturma işlemleri arasında kalıcı hale getirmek için bir araç olarak kullanılsa da durumun genellikle bir oluşturma işleminin ömründen daha uzun süre kalması gerekir. Bu sayfada remember, retain, rememberSaveable ve rememberSerializable API'leri arasındaki fark, hangi API'nin ne zaman seçileceği ve Compose'da hatırlanan ve saklanan değerleri yönetmeyle ilgili en iyi uygulamalar açıklanmaktadır.

Doğru kullanım ömrünü seçme

Compose'da, durumu oluşturmalar arasında ve sonrasında kalıcı hale getirmek için kullanabileceğiniz çeşitli işlevler vardır: remember, retain, rememberSaveable ve rememberSerializable. Bu işlevler, kullanım ömürleri ve semantik açısından farklılık gösterir ve her biri belirli durum türlerini depolamak için uygundur. Farklar aşağıdaki tabloda özetlenmiştir:

remember

retain

rememberSaveable, rememberSerializable

Değerler yeniden oluşturma işlemlerinden etkilenmez mi?

✅

✅

✅

Değerler, etkinlik yeniden oluşturulduğunda korunuyor mu?

❌

✅

Her zaman aynı (===) örnek döndürülür.

✅

Eşdeğer (==) bir nesne döndürülür. Bu, muhtemelen serileştirilmiş bir kopyadır.

Değerler işlem sonlandırmadan etkilenmiyor mu?

❌

❌

✅

Desteklenen veri türleri

Tümü

Etkinlik yok edilirse sızdırılacak nesnelere referans vermemelidir.

Serileştirilebilir olmalıdır
(özel bir Saver veya kotlinx.serialization ile)

Kullanım alanları

  • Oluşturma kapsamında olan nesneler
  • Birleştirilebilir öğeler için yapılandırma nesneleri
  • Kullanıcı arayüzü doğruluğunu kaybetmeden yeniden oluşturulabilecek durum
  • Önbellekler
  • Uzun ömürlü veya "yönetici" nesneler
  • Kullanıcı girişi
  • Metin alanı girişi, kaydırma durumu, açma/kapatma düğmeleri vb. dahil olmak üzere uygulama tarafından yeniden oluşturulamayan durumlar.

remember

remember, Compose'da durumu depolamanın en yaygın yoludur. remember ilk kez çağrıldığında, verilen hesaplama yürütülür ve hatırlanır. Yani, oluşturulabilir öğe tarafından gelecekte yeniden kullanılmak üzere Oluşturma tarafından depolanır. Bir composable yeniden oluşturulduğunda kodunu tekrar yürütür ancak remember çağrıları, hesaplamayı tekrar yürütmek yerine önceki oluşturma işlemindeki değerlerini döndürür.

Bir composable işlevin her örneğinin, konumsal ezberleme olarak adlandırılan kendi hatırlanan değerleri vardır. Hatırlanan değerler, yeniden oluşturma işlemlerinde kullanılmak üzere ezberlendiğinde oluşturma hiyerarşisindeki konumlarına bağlanır. Bir composable farklı konumlarda kullanılıyorsa oluşturma hiyerarşisindeki her örneğin kendi hatırlanan değerleri vardır.

Hatırlanan bir değer artık kullanılmadığında unutulur ve kaydı silinir. Hatırlanan değerler, oluşturma hiyerarşisinden kaldırıldığında (bir değerin kaldırılıp key composable veya MovableContent kullanılmadan farklı bir konuma taşınmak üzere yeniden eklenmesi dahil) veya farklı key parametreleriyle çağrıldığında unutulur.

Mevcut seçenekler arasında remember, en kısa kullanım ömrüne sahiptir ve bu sayfada açıklanan dört ezberleme işlevi arasında değerleri en erken unutan işlevdir. Bu nedenle, aşağıdaki durumlarda kullanılması en uygundur:

  • Kaydırma konumu veya animasyon durumu gibi dahili durum nesneleri oluşturma
  • Her yeniden oluşturmada maliyetli nesne yeniden oluşturma işleminden kaçınma

Ancak şunlardan kaçınmanız gerekir:

  • Hatırlanan nesneler, etkinlik yapılandırması değişiklikleri ve sistem tarafından başlatılan işlem sonlandırma sırasında unutulduğundan, kullanıcı girişleri remember ile depolanır.

rememberSaveable ve rememberSerializable

rememberSaveable ve rememberSerializable, remember'yi temel alır. Bu kılavuzda ele alınan memoization işlevleri arasında en uzun kullanım ömrüne sahip olanlar bunlardır. Yeniden oluşturma işlemlerinde nesneleri konumsal olarak ezberlemenin yanı sıra değerleri kaydederek yapılandırma değişiklikleri ve işlem sonlandırma (sistem, uygulamanız arka plandayken işlemini sonlandırdığında, genellikle ön planda çalışan uygulamalar için bellek boşaltmak amacıyla veya kullanıcı uygulamanız çalışırken izinleri iptal ettiğinde) dahil olmak üzere etkinlik yeniden oluşturma işlemlerinde geri yüklenebilmelerini sağlar.

rememberSerializable, rememberSaveable ile aynı şekilde çalışır ancak kotlinx.serialization kitaplığıyla serileştirilebilen karmaşık türlerin kalıcı olmasını otomatik olarak destekler. Türünüz @Serializable ile işaretlenmişse (veya işaretlenebiliyorsa) rememberSerializable, diğer tüm durumlarda rememberSaveable seçeneğini belirleyin.

Bu nedenle hem rememberSaveable hem de rememberSerializable, metin alanı girişi, kaydırma konumu, açma/kapatma durumları vb. dahil olmak üzere kullanıcı girişiyle ilişkili durumu depolamak için mükemmel adaylardır. Kullanıcının yerini kaybetmemesi için bu durumu kaydetmeniz gerekir. Genel olarak, uygulamanızın veritabanı gibi başka bir kalıcı veri kaynağından alamadığı tüm durumları ezberlemek için rememberSaveable veya rememberSerializable kullanmanız gerekir.

rememberSaveable ve rememberSerializable, ezberlenen değerlerini bir Bundle içinde serileştirerek kaydeder. Bunun iki sonucu vardır:

  • Memoize ettiğiniz değerler, şu veri türlerinden biri veya daha fazlasıyla temsil edilebilir olmalıdır: Temel öğeler (Int, Long, Float, Double dahil), String veya bu türlerden herhangi birinin dizileri.
  • Kayıtlı bir değer geri yüklendiğinde, (==) değerine eşit olan ancak oluşturma işleminin daha önce kullandığı referansla (===) aynı olmayan yeni bir örnek oluşturulur.

kotlinx.serialization kullanmadan daha karmaşık veri türlerini depolamak için nesnenizi desteklenen veri türlerine göre serileştiren ve serileştirmeyi kaldıran özel bir Saver uygulayabilirsiniz. Compose'un State, List, Map, Set gibi yaygın veri türlerini hazır olarak anladığını ve bunları sizin adınıza otomatik olarak desteklenen türlere dönüştürdüğünü unutmayın. Aşağıda, Size sınıfı için bir Saver örneği verilmiştir. Size öğesinin tüm özelliklerini listSaver kullanarak bir listeye paketleyerek uygulanır.

data class Size(val x: Int, val y: Int) {
    object Saver : androidx.compose.runtime.saveable.Saver<Size, Any> by listSaver(
        save = { listOf(it.x, it.y) },
        restore = { Size(it[0], it[1]) }
    )
}

@Composable
fun rememberSize(x: Int, y: Int) {
    rememberSaveable(x, y, saver = Size.Saver) {
        Size(x, y)
    }
}

retain

retain API'si, değerlerini ne kadar süreyle ezberlediği açısından remember ile rememberSaveable/rememberSerializable arasında yer alır. Saklanan değerler, hatırlanan değerlerden farklı bir yaşam döngüsüne sahip olduğu için farklı şekilde adlandırılır.

Bir değer saklandığında hem konumsal olarak ezberlenir hem de uygulamanın kullanım ömrüne bağlı ayrı bir kullanım ömrü olan ikincil bir veri yapısına kaydedilir. Saklanan bir değer, serileştirilmeden yapılandırma değişikliklerinden etkilenmez ancak işlem sonlandırmadan etkilenir. Oluşturma hiyerarşisi yeniden oluşturulduktan sonra bir değer kullanılmazsa saklanan değer kullanımdan kaldırılır (bu, retain'nin unutulmaya eşdeğerdir).

rememberSaveable değerinden daha kısa olan bu yaşam döngüsü karşılığında retain, serileştirilemeyen değerleri (ör. lambda ifadeleri, akışlar ve bit eşlemler gibi büyük nesneler) kalıcı hale getirebilir. Örneğin, yapılandırma değişikliği sırasında medya oynatmanın kesintiye uğramasını önlemek için bir medya oynatıcıyı (ör. ExoPlayer) yönetmek üzere retain kullanabilirsiniz.

@Composable
fun MediaPlayer() {
    // Use the application context to avoid a memory leak
    val applicationContext = LocalContext.current.applicationContext
    val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { /* ... */ }.build() }
    // ...
}

retain - ViewModel

Hem retain hem de ViewModel, yapılandırma değişikliklerinde nesne örneklerini koruma konusunda en çok tercih edilen yeteneklerinde benzer işlevler sunar. retain veya ViewModel kullanma tercihi, kalıcı hale getirdiğiniz değerin türüne, kapsamının nasıl olması gerektiğine ve ek işlevlere ihtiyacınız olup olmadığına bağlıdır.

ViewModel, genellikle uygulamanızın kullanıcı arayüzü ile veri katmanları arasındaki iletişimi kapsayan nesnelerdir. Mantığı composable işlevlerinizden çıkarmanıza olanak tanır. Bu da test edilebilirliği artırır. ViewModel, ViewModelStore içinde tekil olarak yönetilir ve saklanan değerlerden farklı bir kullanım ömrüne sahiptir. ViewModel, ViewModelStore yok edilene kadar etkin kalır. Saklanan değerler ise içerik, oluşturmadan kalıcı olarak kaldırıldığında kullanımdan kaldırılır (örneğin, yapılandırma değişikliği için bu, kullanıcı arayüzü hiyerarşisi yeniden oluşturulursa ve saklanan değer, oluşturma yeniden oluşturulduktan sonra kullanılmazsa saklanan değerin kullanımdan kaldırıldığı anlamına gelir).

ViewModel, Dagger ve Hilt ile bağımlılık ekleme için kullanıma hazır entegrasyonlar, SavedState ile entegrasyon ve arka plan görevlerini başlatmak için yerleşik eş yordam desteği de içerir. Bu sayede ViewModel, arka plan görevlerini ve ağ isteklerini başlatmak, projenizdeki diğer veri kaynaklarıyla etkileşim kurmak ve isteğe bağlı olarak, ViewModel içindeki yapılandırma değişiklikleri boyunca korunması ve işlem sonlandırmadan etkilenmemesi gereken, görev açısından kritik kullanıcı arayüzü durumunu yakalayıp kalıcı hale getirmek için ideal bir yer haline gelir.

retain, belirli composable örnekleri için kapsamlandırılmış ve kardeş composable'lar arasında yeniden kullanım veya paylaşım gerektirmeyen nesneler için en uygun yöntemdir. ViewModel, kullanıcı arayüzü durumunu depolamak ve arka plan görevlerini gerçekleştirmek için iyi bir yerdir. retain ise önbellekler, gösterim izleyiciler ve analizler, AndroidView'lere bağımlılıklar ve Android OS ile etkileşimde bulunan veya ödeme işleyiciler ya da reklamcılık gibi üçüncü taraf kitaplıklarını yöneten diğer nesneler gibi kullanıcı arayüzü tesisatı için nesneleri depolamak üzere iyi bir adaydır.

Modern Android uygulama mimarisi önerilerinin dışında özel uygulama mimarisi kalıpları tasarlayan ileri düzey kullanıcılar için: retain, şirket içi "ViewModel benzeri" bir API oluşturmak için de kullanılabilir. Eş yordamlar ve kaydedilmiş durum desteği hazır olarak sunulmasa da retain, bu özelliklerin üzerine inşa edildiği ViewModel benzerlerinin yaşam döngüsü için yapı taşı görevi görebilir. Böyle bir bileşenin nasıl tasarlanacağıyla ilgili ayrıntılar bu kılavuzun kapsamı dışındadır.

retain

ViewModel

Kapsam belirleme

Paylaşılan değer yoktur. Her değer, oluşturma hiyerarşisindeki belirli bir noktada tutulur ve bu noktayla ilişkilendirilir. Aynı türü farklı bir konumda tutmak her zaman yeni bir örnek üzerinde işlem yapar.

ViewModel'ler, ViewModelStore içinde tekil öğelerdir.

Yıkım

Oluşturma hiyerarşisinden kalıcı olarak ayrılırken

ViewModelStore temizlendiğinde veya imha edildiğinde

Ek işlevler

Nesne, oluşturma hiyerarşisinde olduğunda veya olmadığında geri çağırma alabilir.

Yerleşik coroutineScope, SavedStateHandle desteği, Hilt kullanılarak eklenebilir

Sahibi

RetainedValuesStore

ViewModelStore

Kullanım alanları

  • Kullanıcı arayüzüne özgü değerleri, her bir composable örneği için yerel olarak kalıcı hale getirme
  • Gösterim izleyici (muhtemelen RetainedEffect üzerinden)
  • Özel bir "ViewModel benzeri" mimari bileşeni tanımlamak için yapı taşı
  • Hem kod düzeni hem de test için kullanıcı arayüzü ile veri katmanları arasındaki etkileşimleri ayrı bir sınıfa ayırma
  • Flow'leri State nesnelerine dönüştürme ve yapılandırma değişiklikleriyle kesintiye uğramaması gereken askıya alma işlevlerini çağırma
  • Tüm ekranlar gibi büyük kullanıcı arayüzü alanlarında durumları paylaşma
  • View ile birlikte çalışabilirlik

retain ve rememberSaveable veya rememberSerializable öğelerini birleştirin

Bazen bir nesnenin hem retained hem de rememberSaveable veya rememberSerializable karma kullanım ömrüne sahip olması gerekir. Bu durum, nesnenizin ViewModel olması gerektiğinin bir göstergesi olabilir. , ViewModel için kaydedilmiş durum modülü kılavuzunda açıklandığı gibi kaydedilmiş durumu destekleyebilir.

retain ve rememberSaveable veya rememberSerializable aynı anda kullanılabilir. Her iki yaşam döngüsünü doğru şekilde birleştirmek, süreci önemli ölçüde karmaşıklaştırır. Bu kalıbı daha gelişmiş ve özel mimari kalıplarının bir parçası olarak ve yalnızca aşağıdaki koşulların tümü geçerli olduğunda kullanmanızı öneririz:

  • Saklanması veya kaydedilmesi gereken değerlerin karışımından oluşan bir nesne tanımlıyorsunuz (ör.kullanıcı girişini izleyen bir nesne ve diske yazılamayan RAM önbelleği).
  • Durumunuz bir composable ile sınırlı ve ViewModel için tekil kapsam veya kullanım ömrüne uygun değil.

Bu durumların hepsi geçerliyse sınıfınızı üç bölüme ayırmanızı öneririz: kaydedilen veriler, saklanan veriler ve kendi durumu olmayan ve durumu buna göre güncellemek için saklanan ve kaydedilen nesnelere yetki veren bir "aracı" nesne. Bu kalıp şu şekildedir:

@Composable
fun rememberAndRetain(): CombinedRememberRetained {
    val saveData = rememberSerializable(serializer = serializer<ExtractedSaveData>()) {
        ExtractedSaveData()
    }
    val retainData = retain { ExtractedRetainData() }
    return remember(saveData, retainData) {
        CombinedRememberRetained(saveData, retainData)
    }
}

@Serializable
data class ExtractedSaveData(
    // All values that should persist process death should be managed by this class.
    var savedData: AnotherSerializableType = defaultValue()
)

class ExtractedRetainData {
    // All values that should be retained should appear in this class.
    // It's possible to manage a CoroutineScope using RetainObserver.
    // See the full sample for details.
    var retainedData = Any()
}

class CombinedRememberRetained(
    private val saveData: ExtractedSaveData,
    private val retainData: ExtractedRetainData,
) {
    fun doAction() {
        // Manipulate the retained and saved state as needed.
    }
}

Durumu kullanım ömrüne göre ayırdığınızda sorumlulukların ve depolama alanının ayrımı çok netleşir. Kayıt verilerinin, saklama verileri tarafından değiştirilememesi bilinçli bir tercihtir. Bu sayede, savedInstanceState paketi zaten yakalanmış ve güncellenemiyorken kayıt verilerinin güncellenmeye çalışıldığı bir senaryo önlenir. Ayrıca, Compose'u çağırmadan veya bir etkinliğin yeniden oluşturulmasını simüle etmeden yapıcılarınızı test ederek yeniden oluşturma senaryolarını test etmenize olanak tanır.

Bu kalıbın nasıl uygulanabileceğine dair eksiksiz bir örnek için tam örneğe (RetainAndSaveSample.kt) bakın.

Konumsal memoization ve uyarlanabilir düzenler

Android uygulamaları; telefonlar, katlanabilir cihazlar, tabletler ve masaüstü bilgisayarlar gibi birçok form faktörünü destekleyebilir. Uygulamaların, uyarlanabilir düzenler kullanarak bu form faktörleri arasında sık sık geçiş yapması gerekir. Örneğin, tablette çalışan bir uygulama iki sütunlu liste-ayrıntı düzeni gösterebilir ancak daha küçük bir telefon ekranında gösterildiğinde liste ile ayrıntılar sayfası arasında gezinebilir.

Hatırlanan ve saklanan değerler konumsal olarak ezberlendiğinden, yalnızca oluşturma hiyerarşisinde aynı noktada görünürlerse yeniden kullanılırlar. Düzenleriniz farklı form faktörlerine uyum sağladıkça oluşturma hiyerarşinizin yapısını değiştirebilir ve değerlerin unutulmasına neden olabilir.

ListDetailPaneScaffold ve NavDisplay gibi kullanıma hazır bileşenlerde (Jetpack Navigation 3'ten) bu bir sorun değildir ve durumunuz düzen değişiklikleri boyunca korunur. Form faktörlerine uyum sağlayan özel bileşenlerde, aşağıdakilerden birini yaparak durumun düzen değişikliklerinden etkilenmediğinden emin olun:

  • Durum bilgisi olan composable'ların her zaman oluşturma hiyerarşisinde aynı yerde çağrıldığından emin olun. Uyarlanabilir düzenleri, oluşturma hiyerarşisindeki nesnelerin yerini değiştirmek yerine düzen mantığını değiştirerek uygulayın.
  • Durum bilgisi olan composable'ları sorunsuz bir şekilde yeniden konumlandırmak için MovableContent kullanın. MovableContent örnekleri, hatırlanan ve saklanan değerleri eski konumlarından yeni konumlarına taşıyabilir.

Fabrika işlevlerini hatırlama

Compose kullanıcı arayüzleri composable işlevlerden oluşsa da bir oluşturma işleminin oluşturulması ve düzenlenmesi için birçok nesne kullanılır. Bunun en yaygın örneği, LazyListState kabul eden LazyList gibi kendi durumunu tanımlayan karmaşık composable nesnelerdir.

Compose odaklı nesneleri tanımlarken, hem kullanım ömrü hem de temel girişler dahil olmak üzere amaçlanan hatırlama davranışını tanımlamak için bir remember işlevi oluşturmanızı öneririz. Bu sayede, durumunuzun tüketicileri, oluşturma hiyerarşisinde beklenen şekilde hayatta kalacak ve geçersiz kılınacak örnekleri güvenle oluşturabilir. Oluşturulabilir fabrika işlevi tanımlarken aşağıdaki yönergeleri uygulayın:

  • İşlev adının önüne remember ekleyin. İsteğe bağlı olarak, işlev uygulaması nesnenin retained olmasına bağlıysa ve API hiçbir zaman remember'in farklı bir varyasyonuna bağlı olacak şekilde gelişmeyecekse bunun yerine retain ön ekini kullanın.
  • Durum kalıcılığı seçildiyse ve doğru bir Saver uygulaması yazmak mümkünse rememberSaveable veya rememberSerializable kullanın.
  • Kullanımla alakalı olmayabilecek CompositionLocal'lere dayalı yan etkilerden veya değerleri başlatmaktan kaçının. Durumunuzun oluşturulduğu yer, kullanıldığı yer olmayabilir.

@Composable
fun rememberImageState(
    imageUri: String,
    initialZoom: Float = 1f,
    initialPanX: Int = 0,
    initialPanY: Int = 0
): ImageState {
    return rememberSaveable(imageUri, saver = ImageState.Saver) {
        ImageState(
            imageUri, initialZoom, initialPanX, initialPanY
        )
    }
}

data class ImageState(
    val imageUri: String,
    val zoom: Float,
    val panX: Int,
    val panY: Int
) {
    object Saver : androidx.compose.runtime.saveable.Saver<ImageState, Any> by listSaver(
        save = { listOf(it.imageUri, it.zoom, it.panX, it.panY) },
        restore = { ImageState(it[0] as String, it[1] as Float, it[2] as Int, it[3] as Int) }
    )
}