İçeriğe geç
Tüm yazılar

2026'da Jetpack DataStore: tek bir ayarı bile kaybetmeden SharedPreferences'tan geçiş

SharedPreferences'tan Jetpack DataStore'a geçiş için pratik bir Android rehberi — asenkron tuzaklar, mevcut değerleri koruyan migrasyon yolu ve bunu nasıl test edeceğiniz.

MFKAPPS 4 dk okuma

SharedPreferences hâlâ çalışıyor. Ama ilk erişimde diskten senkron bir okuma yapıyor, derleme zamanında tip güvenliği sunmuyor ve elle bir dinleyici bağlamadan bir değerin değişimini gözlemlemenin bir yolunu vermiyor. Jetpack DataStore bu üç sorunu da çözüyor ve 2026’da Google’ın tek seferlik bir bayrağın ötesindeki her şey için önerdiği varsayılan seçenek bu. Çoğu migrasyonu durduran şey yeni API’yi öğrenmek değil — mevcut kullanıcıları, bir sonraki güncellemede ayarlarını sıfırlamadan yeni depoya taşımak.

SharedPreferences’ta gerçekte ne yanlış

getSharedPreferences().getString(...) senkron ve ucuz görünür, ama bir süreçte ilk erişimde çağıran thread’i gerçek bir dosya okumasıyla bloke edebilir — çoğu zaman uygulama açılışı sırasında ana thread’i. Değişiklikleri bir akış olarak toplamanın yerleşik bir yolu yok; bir OnSharedPreferenceChangeListener kaydedip yaşam döngüsünü elle yönetiyorsunuz. Ve her okuma string tabanlı: bir anahtar adındaki yazım hatası derlenir ama çalışma zamanında sessizce varsayılan değeri döndürerek başarısız olur.

Bunların hiçbiri egzotik uç durumlar değil. Bunlar SharedPreferences’ın normal davranış biçimi ve Preferences DataStore, zihinsel modeli çok fazla değiştirmeden bunları değiştirmek için özel olarak yapıldı.

Preferences DataStore: aynı şekil, güvenli bir şekilde

Bir Preferences DataStore örneği bir kez oluşturulur, tipik olarak Context üzerinde bir extension property olarak:

val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(
    name = "settings"
)

Okumalar bir Flow olarak gelir, böylece değişiklik bildirimlerini kendiniz kurmak yerine ücretsiz elde edersiniz:

val TIMER_MINUTES = intPreferencesKey("timer_minutes")

val timerMinutes: Flow<Int> = context.settingsDataStore.data
    .map { prefs -> prefs[TIMER_MINUTES] ?: 25 }

Yazmalar bir suspend fonksiyonundan geçer, bu yüzden asla yanlışlıkla ana thread’den çağrılmazlar:

suspend fun setTimerMinutes(context: Context, minutes: Int) {
    context.settingsDataStore.edit { prefs ->
        prefs[TIMER_MINUTES] = minutes
    }
}

Bu, SharedPreferences’a yeterince yakın, bu yüzden yeniden yazımın kendisi mekanik. Risk tamamen bir kullanıcının cihazında zaten bulunan verilere ne olduğunda yatıyor.

İnsanların atladığı kısım: mevcut değerleri taşımak

DataStore, tam olarak bunun için bir SharedPreferencesMigration sunuyor ve bunu kaçırmak kolay çünkü onu kullanmanızı hiçbir şey zorlamıyor — uygulamanız onsuz da düzgün derlenir ve çalışır, sonra depolama arka ucunu değiştirdiğiniz güncellemede her ayarı sessizce varsayılana sıfırlar.

val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(
    name = "settings",
    produceMigrations = { context ->
        listOf(SharedPreferencesMigration(context, "settings_prefs"))
    }
)

Migrasyon bir kez çalışır, bu kod gönderildikten sonra DataStore ilk açıldığında: eski SharedPreferences dosyasını okur, her girdiyi yeni Preferences deposuna kopyalar ve eski dosyayı olduğu yerde bırakır (silmez — bunu ayrıca, migrasyonun her yerde çalıştığından emin olduğunuzda kendiniz yapmanız gereken bir karar). Bir anahtarın tipi temiz bir şekilde eşleşmiyorsa — artık bir List istediğiniz bir Set<String> gibi — bunu migrasyonun shouldRunMigration’ında filtreler ya da okuma zamanında bir tip uyuşmazlığının fırlamasına izin vermek yerine açıkça dönüştürürsünüz.

Eski preferences dosyasının adını yanlış almak — özel bir adla oluşturulduysa yaygın bir hata, paket varsayılanı yerine — migrasyonun kopyalayacak hiçbir şey bulamamasına ve her kullanıcının güncellemede varsayılana sıfırlanmasına yol açar. Göndermeden önce, tahmin etmek yerine kod tabanındaki mevcut context.getSharedPreferences("name", MODE_PRIVATE) çağrılarıyla tam adı kontrol edin.

Preferences DataStore yetersiz kaldığında

Preferences DataStore düz, string tabanlı bir anahtar alanı tutar — threading ve gözlemlenebilirlik sorunlarını çözdü ama tip güvenliği sorununu çözmedi. Proto DataStore, anahtar-değer torbasını bir kez bir .proto dosyasında tanımladığınız bir şemayla değiştirir, böylece bir ayarlar nesnesi ya şemayla eşleşir ya da derlenmez — çalışma zamanında yazım hatası yapılmış bir anahtardan gelen bir null sonuç yok. Bir ayarlar ekranı birkaç bayrağın ötesine büyüdüğünde, ya da iç içe nesneler (kendi ses, titreşim ve sessiz saatler alanlarına sahip bir bildirim tercihi) anahtar alanında tek bir yapılandırılmış değer yerine üç ya da dört ayrı ad alanına sahip anahtar olarak belirmeye başladığında, ekstra kuruluma değer. Mintly’de, zamanlayıcının ses, titreşim ve otomatik yeniden başlatma ayarları tam olarak bu nedenle küçük bir Proto DataStore mesajına taşındı — ilgili ayarların birlikte okunup yazılması gerektiğinde, düz bir anahtar-değer deposu sizinle savaşmaya başlar.

API’yi değil, migrasyonu test etmek

Yeni okuma/yazma kodu test etmeyi atlayacak kadar basit. Migrasyon değil — bu, gerçek kullanıcı verisine karşı tam olarak bir kez, sessizce çalışan ve yanlışsa yeniden deneme şansı olmayan parça. Minimal bir test gerçek bir SharedPreferences dosyası oluşturur, migrasyon bağlıyken bir DataStore açar ve değerlerin hayatta kaldığını doğrular:

@Test
fun migration_preservesExistingTimerSetting() = runTest {
    val prefs = context.getSharedPreferences("settings_prefs", Context.MODE_PRIVATE)
    prefs.edit().putInt("timer_minutes", 45).commit()

    val dataStore = PreferenceDataStoreFactory.create(
        migrations = listOf(SharedPreferencesMigration(context, "settings_prefs")),
        produceFile = { File(context.filesDir, "test_settings.preferences_pb") }
    )

    val minutes = dataStore.data.first()[intPreferencesKey("timer_minutes")]
    assertEquals(45, minutes)
}

Bunu şu anda üretimde bulunan her anahtara karşı bir kez çalıştırın, sadece yeni bir özellik dalındakilere değil — migrasyon, bu yeniden yazımdan yıllar önce gönderilen özelliklerden gelen ayarlar dahil, gerçek bir cihazın biriktirdiği her şeyi taşımak zorunda.

Kontrol listesi

Bir SharedPreferences’tan DataStore’a migrasyonu birleştirmeden önce: eski preferences dosyası adı kod tabanındaki gerçek getSharedPreferences() çağrısına karşı doğrulanmış, SharedPreferencesMigration şu anda kullanımda olan her anahtar için bağlanmış, bir test eski formatı oluşturuyor ve her değerin hayatta kaldığını doğruluyor, ve telemetri migrasyonun kurulu tabanda çalıştığını doğrulayana kadar eski dosyaya dokunulmuyor. Kullanıcıların hiç fark etmemesi gereken bir değişiklik için küçük bir ekstra özen — bir ayarlar migrasyonu için tam olarak amaçlanan da bu.