Jetpack Compose ekranlarında process death sonrası kaybolan filtre, taslak ve seçim durumlarını SavedStateHandle, Saver ve test senaryolarıyla yönetin. Android MVVM mimarisinde hangi verinin Bundle'a yazılacağını somut örneklerle ayırın.
Android Eğitimi: Compose State'ini Process Death Sonrası Korumak
Android eğitimi için state katmanlarını doğru ayırmak
Bir android eğitimi içinde remember, rememberSaveable ve ViewModel'i aynı problem için alternatifler gibi anlatmak hatalıdır. remember yalnızca mevcut Composition ömründe yaşar; Activity yeniden yaratıldığında kaybolur. rememberSaveable, değer Bundle'a çevrilebiliyorsa SavedStateRegistry üzerinden geri yükler. Ekranın iş kuralı durumu ise ViewModel'de tutulmalı, process death sonrasında tekrar kurulması gereken küçük kullanıcı girdileri SavedStateHandle ile saklanmalıdır. Örneğin ağdan gelen 500 satırlık ürün listesini Bundle'a yazmak yerine yalnızca categoryId, query ve seçili sıralama değerini koruyun; ViewModel bu anahtarlarla repository sorgusunu tekrar çalıştırır.
data class ProductFilter(
val query: String = "",
val categoryId: Long? = null,
val sort: Sort = Sort.POPULAR
)
enum class Sort { POPULAR, PRICE_ASC, PRICE_DESC }
@Composable
fun FilterBar() {
var expanded by rememberSaveable { mutableStateOf(false) }
// expanded sadece arayuz ayrintisidir; ViewModel state'i degildir.
IconButton(onClick = { expanded = !expanded }) {
Icon(Icons.Default.Tune, contentDescription = "Filtre")
}
}Buradaki incelik şudur: rememberSaveable ile korunan expanded değeri, kullanıcının görevini sürdürmesi için anlamlı olabilir; fakat seçili ürün ID'lerinden oluşan büyük bir Set<Long> aynı mekanizmaya körlemesine verilmemelidir. Android, Activity durumunu IPC için Bundle olarak parcel eder ve işlem başına paylaşılan Binder transaction sınırına yaklaşmak TransactionTooLargeException ile sonuçlanabilir. Bu nedenle bir state alanını kaydetmeden önce yeniden üretilebilir mi, hassas mı, taşınabilir boyutta mı sorularını ayrı ayrı yanıtlayın.
Jetpack Compose ve android MVVM mimarisi ile SavedStateHandle
jetpack compose ve android mvvm mimarisi birlikte kullanılırken UI, ViewModel'in StateFlow'unu collectAsStateWithLifecycle() ile okumalıdır. Metin alanının her karakterinde handle'a yazmak teknik olarak çalışır, ancak geri yükleme anahtarını reducer'ın tek yazma noktasında tutmak test edilebilirliği artırır. Aşağıdaki örnekte ViewModel, ilk state'i handle'dan kurar ve her filtre değişiminde yalnızca küçük, parcel edilebilir alanları günceller.
class ProductViewModel(
private val savedStateHandle: SavedStateHandle,
private val repository: ProductRepository
) : ViewModel() {
private val initialFilter = ProductFilter(
query = savedStateHandle["filter.query"] ?: "",
categoryId = savedStateHandle["filter.category_id"],
sort = savedStateHandle.get<String>("filter.sort")
?.let(Sort::valueOf) ?: Sort.POPULAR
)
private val filter = MutableStateFlow(initialFilter)
val uiState: StateFlow<ProductUiState> = filter
.flatMapLatest { current -> repository.observeProducts(current) }
.map(ProductUiState::Content)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ProductUiState.Loading)
fun updateFilter(transform: (ProductFilter) -> ProductFilter) {
val next = transform(filter.value)
filter.value = next
savedStateHandle["filter.query"] = next.query
savedStateHandle["filter.category_id"] = next.categoryId
savedStateHandle["filter.sort"] = next.sort.name
}
}Bu tasarımda flatMapLatest önemlidir: kullanıcı arama metnini değiştirdiğinde eski repository akışı iptal edilir ve eski sorgunun geç gelen sonucu yeni filtreyi ezmez. Ancak repository çağrınız iptal kooperatif değilse, örneğin callback tabanlı bir SDK coroutine iptalini dinlemiyorsa, eski isteği ayrıca iptal edecek bir CancellationSignal veya SDK çağrısı gerekir. UI tarafında yaşam döngüsü farkındalığı için androidx.lifecycle:lifecycle-runtime-compose içindeki API'yi kullanın.
@Composable
fun ProductRoute(viewModel: ProductViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
ProductScreen(
state = state,
onQueryChanged = { query ->
viewModel.updateFilter { it.copy(query = query) }
}
)
}Kotlin eğitimi kapsamında özel Saver ve Bundle boyutu
Bir kotlin eğitimi veya kotlin kursu projesinde sık görülen hata, Compose'a doğrudan domain nesnesi vermektir. Bir Money veya LocalDate nesnesi otomatik olarak saveable değildir. UI için gereken küçük değeri açık bir Saver ile primitive bir forma dönüştürün. Bu dönüşüm, Parcelable eklemekten daha görünür bir sözleşme sağlar ve domain katmanını Android bağımlılığından uzak tutar.
data class PriceRange(val minCents: Long?, val maxCents: Long?)
val PriceRangeSaver = listSaver<PriceRange, Long?>(
save = { listOf(it.minCents, it.maxCents) },
restore = { values -> PriceRange(values[0], values[1]) }
)
@Composable
fun PriceFilter() {
var range by rememberSaveable(stateSaver = PriceRangeSaver) {
mutableStateOf(PriceRange(null, null))
}
// range'i UI'da kullanin; kalici filtreyi ViewModel event'i ile ayri guncelleyin.
}Kaydedilecek state'in boyutunu tahmin etmek yerine debug derlemesinde Parcel ile ölçün. Bu ölçüm Binder'ın kalan paylaşımlı alanını göstermez, fakat tek bir ekranın Bundle'a yüzlerce KB yazdığını erkenden yakalar. Ölçümü gerçek kullanıcı verisiyle, örneğin 2.000 seçili öğe ve uzun arama metniyle çalıştırın. Sonuç büyüyorsa ID listesini diskte Room tablosuna yazın veya sunucudaki taslağın anahtarını saklayın; token, parola ve ödeme verisini SavedStateHandle'a hiç koymayın.
fun Bundle.marshalledSizeBytes(): Int {
val parcel = Parcel.obtain()
return try {
parcel.writeBundle(this)
parcel.marshall().size
} finally {
parcel.recycle()
}
}
val bytes = bundleOf("filter.query" to query).marshalledSizeBytes()
Log.d("SavedStateSize", "filter bundle=$bytes bytes")Android Studio eğitimi: config change ile process death testini ayırmak
Bir android studio eğitimi sırasında yalnızca ActivityScenario.recreate() testi yazmak yeterli değildir. Bu çağrı yapılandırma değişimini simüle eder ve aynı ViewModelStore'un tekrar kullanılabildiği senaryoları kapsayabilir; gerçek process death'te ViewModel bellekte kalmaz. Önce bir instrumented test ile kullanıcı akışını ve recreate() sonrası görünümü doğrulayın, ardından emülatörde uygulama arka plandayken işlemi ADB ile öldürüp görevi yeniden açarak ikinci doğrulamayı yapın.
@Test
fun query_is_restored_after_activity_recreation() {
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
onView(withHint("Ara")).perform(typeText("kamera"))
scenario.recreate()
onView(withHint("Ara")).check(matches(withText("kamera")))
}
}Emülatör veya fiziksel test cihazında paket adını gerçek değerle değiştirin. Önce uygulamada arama filtresini girin, Home ile arka plana alın, sonra aşağıdaki komutu çalıştırın ve Recents ekranından aynı görevi açın. am force-stop kullanmayın: force-stop, alarm ve pending intent davranışlarını da engellediği için kullanıcının normal düşük bellek kaynaklı process death akışını temsil etmez. Test sonucunu Android Studio Logcat'te ViewModel init logu ve ekrandaki geri yüklenmiş filtre ile doğrulayın.
adb shell am kill com.example.catalog
adb shell pidof com.example.catalog
# pidof bos donmeli; ardindan uygulamayi Recents ekranindan yeniden acin.Mobil uygulama geliştirme eğitimi ve play store yayınlama öncesi kontrol
Bir mobil uygulama geliştirme eğitimi veya android kursu projesini üretime taşırken saved state anahtarlarını public sözleşme kabul edin. "filter.sort" anahtarındaki enum adını değiştirirseniz, önceki görevden geri dönen kullanıcıda Sort.valueOf() exception üretebilir. Güvenli geri dönüş için parse işlemini varsayılan değere düşürün ve eski değerleri açıkça eşleyin. Bu, sürüm yükseltmesi sonrası task restore çökmesini önler.
private fun parseSort(raw: String?): Sort = when (raw) {
"POPULAR" -> Sort.POPULAR
"PRICE_ASC" -> Sort.PRICE_ASC
"PRICE_DESC" -> Sort.PRICE_DESC
"CHEAP_FIRST" -> Sort.PRICE_ASC // eski anahtar uyumlulugu
else -> Sort.POPULAR
}play store yayınlama öncesi kapalı test kanalında en az şu senaryoları kayıt altına alın: döndürme, karanlık tema değişimi, arka planda process kill, düşük depolama alanı ve uygulama güncellemesinden sonra eski task'a dönüş. Firebase Crashlytics'te IllegalArgumentException olaylarını saved_state_key özel anahtarıyla etiketlemek, enum veya Bundle dönüşümünden gelen geri yükleme hatalarını sürüm bazında ayırmanızı sağlar. Bu kontrol listesi, state'i ekranda göstermekten çok daha zor olan eski görevlerin yeni uygulama koduyla açılması problemini hedefler.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
android eğitim projesinde rememberSaveable mi SavedStateHandle mı kullanmalıyım?
Açılır menünün açık olması gibi yalnızca Compose UI ayrıntıları için rememberSaveable kullanın. Arama sorgusu, filtre veya seçili sekme gibi ViewModel'in repository sorgusunu etkileyen değerleri SavedStateHandle'a yazın. Büyük listeyi değil, listeyi yeniden yükletecek ID veya sorgu parametresini saklayın.
jetpack compose process death testi Android Studio'da nasıl yapılır?
Önce ActivityScenario.recreate() ile yapılandırma değişimini test edin. Ardından uygulamayı arka plana alın, adb shell am kill paket.adi komutunu çalıştırın ve Recents üzerinden aynı görevi açın. am force-stop normal process death davranışını temsil etmediği için bu kontrol için uygun değildir.
android mvvm mimarisi içinde SavedStateHandle'a hangi veriler yazılmamalı?
Access token, parola, kart verisi, bitmap, ağ yanıtı ve binlerce öğelik koleksiyon yazılmamalıdır. SavedStateHandle Bundle üzerinden parcel edildiğinden büyük yük TransactionTooLargeException riski taşır. Bundle.marshalledSizeBytes() benzeri bir debug ölçümüyle boyutu kontrol edin ve büyük veriyi Room veya repository'den yeniden yükleyin.
kotlin kursu uygulamasında enum SavedStateHandle'dan güvenli nasıl okunur?
Enum adını doğrudan valueOf ile okumak yerine when tabanlı parse fonksiyonu kullanın ve bilinmeyen değer için varsayılan seçin. Böylece eski uygulama sürümünün yazdığı enum değeri, güncel kodda yeniden adlandırılmış olsa bile task restore sırasında uygulamayı çökertmez.
AI / LLM Discovery
Bu makale Opendart Akademi Android eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


