• 22.08.2026 23:16:58
  • Admin Admin

Jetpack Compose ekranlarında process death sonrası filtre, taslak ve seçili öğe durumunu geri getirmek için SavedStateHandle, rememberSaveable ve cihaz üstü test akışını uygulayın.

Jetpack Compose'da Process Death Sonrası Durumu Geri Yüklemek

Jetpack Compose ve process death senaryosunu doğru yeniden üretmek

Bir Activity yeniden oluşturulduğunda yapılan test, process death testi değildir. ActivityScenario.recreate() aynı process içindeki ViewModel örneğini veya bellekteki repository cache'ini koruyabilir. Jetpack Compose ile yazılmış bir arama ekranında önce metin girin, uygulamayı arka plana alın, sonra hedef paketin process'ini öldürüp launcher üzerinden tekrar açın. Bu akış, yalnızca Bundle içine yazılmış durumun geri dönüp dönmediğini gösterir.

adb shell input keyevent KEYCODE_HOME
adb shell am kill com.example.catalog
adb shell monkey -p com.example.catalog   -c android.intent.category.LAUNCHER 1

adb shell am force-stop com.example.catalog bu deney için yanlış araçtır. Force-stop, uygulamanın görevini ve alarm, broadcast gibi yeniden başlatma davranışlarını da iptal eder; kullanıcı uygulamayı son uygulamalar ekranından döndürdüğünde görülen normal process death davranışını taklit etmez. Android Studio Logcat'te ActivityTaskManager, uygulamanın process id'si ve ViewModel'e eklediğiniz geçici bir instance id ile akışı doğrulayın.

class SearchViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val instanceId = UUID.randomUUID().toString()

    init {
        Log.d("SearchVm", "created instance=$instanceId")
    }
}

Bu ayrım, android eğitimi içeriklerinde sık atlanır: konfigurasyon değişikliği için çalışan remember, düşük bellek nedeniyle process öldürüldüğünde çalışmaz. Test matrisi en az üç satır içermelidir: ekran döndürme, arka planda process kill ve görevden normal geri dönüş. Her satırda arama filtresi, seçili ürün id'si ve yarım kalmış form taslağı için beklenen sonucu ayrı ayrı yazın.

Android MVVM mimarisi içinde SavedStateHandle sınırını çizmek

Android MVVM mimarisi kullanırken SavedStateHandle'a tüm UI state nesnesini yazmak yerine, ekranı yeniden kurmaya yetecek küçük anahtarları yazın. Örneğin ürün detay ekranında productId, seçili sekme ve arama sorgusu saklanabilir; 300 ürünlük liste, bitmap veya ağ yanıtı saklanmamalıdır. Saved state Activity'nin kaydedilen instance state Bundle'ına serileşir. Büyük veri hem ana thread serileştirme maliyeti yaratır hem de Binder transaction sınırına yaklaşarak TransactionTooLargeException üretebilir.

Aşağıdaki ViewModel, sorguyu state handle'dan okur, repository akışını sorguya göre yeniden başlatır ve sadece sorgunun kendisini kaydeder. Process yeniden geldiğinde ağ veya Room verisi repository tarafından tekrar okunur; eski UI modelini Bundle'dan taşımaya çalışmaz.

class ProductSearchViewModel(
    private val handle: SavedStateHandle,
    private val repository: ProductRepository
) : ViewModel() {
    private val query = handle.getStateFlow("query", "")

    val uiState: StateFlow<SearchUiState> = query
        .debounce(250)
        .distinctUntilChanged()
        .flatMapLatest { value ->
            repository.search(value)
                .map { products -> SearchUiState.Ready(value, products) }
                .onStart { emit(SearchUiState.Loading(value)) }
        }
        .stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(5_000),
            SearchUiState.Loading("")
        )

    fun updateQuery(value: String) {
        handle["query"] = value
    }
}

Buradaki kritik incelik flatMapLatest'dir. Kullanıcı "kotlin" yazarken eski "kot" isteği geç dönerse, doğrudan launch { repository.search(...) } yaklaşımı eski sonucun yeni sorguyu ezmesine yol açabilir. flatMapLatest önceki Flow koleksiyonunu iptal eder. Repository Retrofit kullanıyorsa iptalin HTTP çağrısına ulaşması için suspend API'nin coroutine cancellation desteklemesi gerekir; bloklayan, callback tabanlı özel istemcilerde bunu ayrıca doğrulayın.

Kotlin eğitimi veya kotlin kursu projelerinde yaygın hata, SavedStateHandle.getStateFlow() ile dönen değeri mutable UI state gibi kullanmaktır. Değeri yalnızca handle["query"] = value üzerinden değiştirin. Böylece yazma noktası tek olur ve testte handle başlangıç değeri doğrudan verilebilir.

@Test
fun restores_query_from_saved_state() = runTest {
    val handle = SavedStateHandle(mapOf("query" to "compose"))
    val viewModel = ProductSearchViewModel(handle, fakeRepository)

    assertEquals("compose", viewModel.uiState.first().query)
}

Jetpack Compose'da rememberSaveable ve özel Saver seçimi

ViewModel'e ait olmayan, tamamen yerel UI ayrıntıları için rememberSaveable kullanın. Açık-kapalı filtre paneli, text field imleç durumu veya geçici sıralama seçimi buna örnektir. Salt remember composable ağacından ayrılana ya da process öldürülene kadar yaşar; rememberSaveable ise SavedStateRegistry aracılığıyla Bundle'a yazılabilen değerleri geri kurar.

@Composable
fun SearchToolbar(
    initialQuery: String,
    onQueryChanged: (String) -> Unit
) {
    var filtersExpanded by rememberSaveable { mutableStateOf(false) }

    Column {
        SearchField(
            value = initialQuery,
            onValueChange = onQueryChanged
        )
        FilterToggle(
            expanded = filtersExpanded,
            onClick = { filtersExpanded = !filtersExpanded }
        )
    }
}

Parcelable olmayan bir değeri doğrudan rememberSaveable içine koymak cihazda çalışıyor gibi görünse bile state kaydı sırasında hata verebilir. Özel bir filtreyi küçük primitive alanlara indirgemek için listSaver kullanın. Bu yaklaşım, domain model değiştiğinde eski Bundle'dan gelen eksik alanı varsayılanla okumayı da mümkün kılar.

data class PriceFilter(val min: Int?, val max: Int?, val inStock: Boolean)

val PriceFilterSaver = listSaver<PriceFilter, Any?>(
    save = { listOf(it.min, it.max, it.inStock) },
    restore = { values ->
        PriceFilter(
            min = values[0] as Int?,
            max = values[1] as Int?,
            inStock = values[2] as Boolean
        )
    }
)

@Composable
fun FilterPanel() {
    var filter by rememberSaveable(stateSaver = PriceFilterSaver) {
        mutableStateOf(PriceFilter(null, null, false))
    }
    // filter güncellemeleri burada yapılır
}

LazyColumn içindeki satır durumlarında ek bir tuzak vardır. Öğeler yeniden sıralandığında index'i saveable key olarak kullanmak, bir ürünün checkbox durumunu başka ürüne taşıyabilir. items(products, key = { it.id }) kullanın ve gerekiyorsa rememberSaveable(product.id) ile stabil domain id'sini anahtar yapın. Bu detay, android studio eğitimi sırasında emulator'da fark edilmeyip gerçek katalog sıralaması değiştiğinde görünür.

Mobil uygulama geliştirme eğitimi için geri yükleme doğrulama akışı

Manuel adb deneyi tek başına regresyon koruması değildir. Compose UI testinde kullanıcı akışını hazırlayın, ardından CI cihazında process kill komutunu çalıştırıp launcher Activity'yi tekrar açın. Gradle Managed Devices kullanıyorsanız test cihazını sabitleyerek emulator imajı ve ekran çözünürlüğü farklarından kaynaklanan flaky seçicileri azaltabilirsiniz.

android {
    testOptions {
        managedDevices {
            devices {
                create<com.android.build.api.dsl.ManagedVirtualDevice>("pixelApi") {
                    device = "Pixel 2"
                    apiLevel = 35
                    systemImageSource = "aosp"
                }
            }
        }
    }
}

Test kodunda shell komutunu instrumentation'ın UiAutomation.executeShellCommand() API'siyle çalıştırabilirsiniz. Komuttan sonra Activity referansını tekrar kullanmayın; öldürülen process'e ait Espresso veya Compose rule nesneleri geçersizdir. Yeniden başlatma sonrasında yeni test oturumu veya UI Automator ile ekrandaki kalıcı metni doğrulayın. adb shell uiautomator dump çıktısı, erişilebilirlik etiketi eksik olduğu için bulunamayan düğmeleri teşhis etmekte yararlıdır.

Ölçülebilir kabul kriteri belirleyin: process kill sonrası sorgu alanı aynı metni göstermeli, repository en fazla bir başlangıç sorgusu ve bir geri yüklenmiş sorgu isteği üretmeli, seçili ürün id'si geçersizse ekran güvenli bir boş duruma düşmelidir. Fake repository'ye çağrı sayacı ekleyip bunu test edin. Bu, android eğitim veya android kursu projelerinde sadece ekranda metin göründüğünü değil, geri yüklemenin gereksiz ağ çağrısı üretmediğini de kanıtlar.

Play Store yayınlama öncesi Bundle boyutu ve sürüm geçişi riskleri

Play Store yayınlama öncesinde process death akışını release build ile kontrol edin. Debug ile release arasındaki R8 farkı çoğunlukla SavedStateHandle primitive değerlerini bozmaz, ancak Parcelable, reflection veya JSON ile saklanan karmaşık sınıflar shrink sonrası farklı davranabilir. Release APK'yı bağlı cihaza yüklemek için aşağıdaki komutu kullanın ve aynı adb senaryosunu tekrarlayın.

./gradlew :app:assembleRelease
adb install -r app/build/outputs/apk/release/app-release.apk
adb shell am kill com.example.catalog

Bundle'a token, parola, ham ödeme verisi veya erişim yetkisi yazmayın. Saved instance state uygulama process'inin dışına taşınabilir ve cihaz yedeği ya da hata ayıklama süreçlerinde istenmeyen yüzey oluşturur. Bunun yerine kimlik doğrulama için Android Keystore destekli güvenli depolama kullanın, SavedStateHandle'da yalnızca tekrar çözümleyebileceğiniz bir kayıt id'si tutun.

Uygulamanın önceki sürümünden kalmış state ile açılma ihtimali için Saver restore kodunu toleranslı yazın. Örneğin enum adını Bundle'a koymak yerine stabil bir integer veya string wire değeri kullanın; bilinmeyen değerde varsayılan filtreye dönün. Bu yaklaşım, mobil uygulama geliştirme eğitimi kapsamında anlatılan state yönetimini gerçek yayın döngüsüne taşır ve eski task kaydının yeni model şemasını çökertmesini engeller.

Sık Sorulan Sorular

Jetpack Compose'da remember ile rememberSaveable arasındaki process death farkı nedir?

remember yalnızca mevcut composition belleğinde değer tutar. rememberSaveable, SavedStateRegistry üzerinden Bundle'a yazılabilen değerleri Activity yeniden oluşturulduğunda geri verir. Büyük liste veya bitmap yerine ürün id'si, sekme indeksi ve küçük filtre alanları saklayın.

android mvvm mimarisi içinde SavedStateHandle'a tüm UiState yazılmalı mı?

Hayır. SavedStateHandle'a ekranı yeniden kuracak minimum anahtarları yazın: örneğin query, productId ve selectedTab. Listeyi Room veya ağ repository'sinden tekrar üretin. Büyük UiState serileştirmek Binder transaction sınırına yaklaşabilir ve TransactionTooLargeException oluşturabilir.

kotlin eğitimi sırasında Android process death testi nasıl yapılır?

Uygulamayı Home ile arka plana alın, adb shell am kill paket.adı çalıştırın ve monkey ile launcher Activity'yi açın. force-stop kullanmayın; görev ve normal geri dönüş davranışını da temizlediği için gerçekçi olmayan bir senaryo üretir.

android kursu projesinde LazyColumn satır durumunu nasıl korurum?

items çağrısında index yerine stabil domain id'siyle key verin: items(products, key = { it.id }). Satıra özgü saveable state gerekiyorsa rememberSaveable(product.id) kullanın. Sıralama değiştiğinde index anahtarı checkbox veya expanded durumunu yanlış satıra taşıyabilir.

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.

Opendart Akademi llms.txt