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:
|
|
|
|
|---|---|---|---|
Değerler yeniden oluşturma işlemlerinden etkilenmez mi? |
✅ |
✅ |
✅ |
Değerler, etkinlik yeniden oluşturulduğunda korunuyor mu? |
❌ |
✅ Her zaman aynı ( |
✅ Eşdeğer ( |
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 |
Kullanım alanları |
|
|
|
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
rememberile 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,Doubledahil),Stringveya 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.
|
|
|
|---|---|---|
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. |
|
Yıkım |
Oluşturma hiyerarşisinden kalıcı olarak ayrılırken |
|
Ek işlevler |
Nesne, oluşturma hiyerarşisinde olduğunda veya olmadığında geri çağırma alabilir. |
Yerleşik |
Sahibi |
|
|
Kullanım alanları |
|
|
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
ViewModeliç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
MovableContentkullanı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
rememberekleyin. İsteğe bağlı olarak, işlev uygulaması nesneninretainedolmasına bağlıysa ve API hiçbir zamanremember'in farklı bir varyasyonuna bağlı olacak şekilde gelişmeyecekse bunun yerineretainön ekini kullanın. - Durum kalıcılığı seçildiyse ve doğru bir
Saveruygulaması yazmak mümkünserememberSaveableveyarememberSerializablekullanı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) } ) }