Project: /architecture/_project.yaml Book: /architecture/_book.yaml keywords: datastore, architecture, api:JetpackDataStore description: Explore this app architecture guide on data layer libraries to learn about Preferences DataStore and Proto DataStore, Setup, and more. hide_page_heading: true
DataStore Android Jetpack'in bir parçasıdır.
Jetpack DataStore, anahtar-değer çiftlerini veya protocol buffer ile yazılmış nesneleri depolamanıza olanak tanıyan bir veri depolama çözümüdür. DataStore, verileri eşzamansız, tutarlı ve işlemsel olarak depolamak için Kotlin coroutine'lerini ve Flow'u kullanır.
Verileri depolamak için SharedPreferences kullanıyorsanız bunun yerine DataStore'a geçiş yapmayı düşünebilirsiniz.
DataStore API
DataStore arayüzü aşağıdaki API'yi sağlar:
DataStore'dan veri okumak için kullanılabilecek bir akış
val data: Flow<T>DataStore'daki verileri güncelleme işlevi
suspend updateData(transform: suspend (t) -> T)
DataStore Yapılandırmaları
Anahtarları kullanarak verileri depolamak ve verilere erişmek istiyorsanız önceden tanımlanmış bir şema gerektirmeyen ve tür güvenliği sağlamayan Preferences DataStore uygulamasını kullanın. SharedPreferences benzeri bir API'ye sahiptir ancak paylaşılan tercihlerle ilişkili dezavantajları yoktur.
DataStore, özel sınıfları kalıcı hale getirmenize olanak tanır. Bunu yapmak için verilerle ilgili bir şema tanımlamanız ve verileri kalıcı bir biçime dönüştürmek için Serializer sağlamanız gerekir. Protocol Buffers, JSON veya başka bir serileştirme stratejisini kullanmayı seçebilirsiniz.
Kurulum
Uygulamanızda Jetpack DataStore'u kullanmak için, kullanmak istediğiniz uygulamaya bağlı olarak Gradle dosyanıza aşağıdakileri ekleyin:
Preferences DataStore
Gradle dosyanızın bağımlılıklar bölümüne aşağıdaki satırları ekleyin:
Groovy
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation "androidx.datastore:datastore-preferences:1.1.7" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-preferences-core:1.1.7" }
Kotlin
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation("androidx.datastore:datastore-preferences:1.1.7") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-preferences-core:1.1.7") }
İsteğe bağlı RxJava desteği eklemek için aşağıdaki bağımlılıkları ekleyin:
Groovy
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.7" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") }
DataStore
Gradle dosyanızın bağımlılıklar bölümüne aşağıdaki satırları ekleyin:
Groovy
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation "androidx.datastore:datastore:1.1.7" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-core:1.1.7" }
Kotlin
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation("androidx.datastore:datastore:1.1.7") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-core:1.1.7") }
RxJava desteği için aşağıdaki isteğe bağlı bağımlılıkları ekleyin:
Groovy
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.7" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.7") }
İçeriği serileştirmek için Protocol Buffers veya JSON serileştirme bağımlılıklarını ekleyin.
JSON serileştirme
JSON serileştirmeyi kullanmak için Gradle dosyanıza aşağıdakileri ekleyin:
Groovy
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" }
Kotlin
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") }
Protobuf serileştirme
Protobuf serileştirmeyi kullanmak için Gradle dosyanıza aşağıdakileri ekleyin:
Groovy
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation "com.google.protobuf:protobuf-kotlin-lite:4.32.1" } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
Kotlin
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation("com.google.protobuf:protobuf-kotlin-lite:4.32.1") } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
DataStore'u doğru kullanma
DataStore'u doğru şekilde kullanmak için aşağıdaki kuralları her zaman göz önünde bulundurun:
Aynı işlemde belirli bir dosya için hiçbir zaman birden fazla
DataStoreörneği oluşturmayın. Bu işlem, tüm DataStore işlevlerinin bozulmasına neden olabilir. Aynı işlemde belirli bir dosya için birden fazla DataStore etkinse DataStore, verileri okurken veya güncellerkenIllegalStateExceptionhatası verir.DataStore<T>öğesinin genel türü sabit olmalıdır. DataStore'da kullanılan bir türün değiştirilmesi, DataStore'un sağladığı tutarlılığı geçersiz kılar ve yakalanması zor, ciddi hatalara yol açabilir. Değişmezlik, net bir API ve verimli serileme sağlamaya yardımcı olan protokol arabelleklerini kullanmanızı öneririz.Aynı dosya için
SingleProcessDataStoreveMultiProcessDataStorekullanımlarını karıştırmayın.DataStoreöğesine birden fazla işlemden erişmeyi planlıyorsanızMultiProcessDataStorekullanmanız gerekir.
Veri Tanımı
Preferences DataStore
Verileri diske kalıcı olarak kaydetmek için kullanılacak bir anahtar tanımlayın.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
JSON DataStore
JSON veri deposu için, kalıcı olmasını istediğiniz verilere @Serialization ek açıklaması ekleyin.
@Serializable
data class Settings(
val exampleCounter: Int
)
Serializer<T> uygulayan bir sınıf tanımlayın. Burada T, daha önce ek açıklamayı eklediğiniz sınıfın türüdür. Henüz dosya oluşturulmamışsa kullanılacak seri hale getirici için varsayılan bir değer eklediğinizden emin olun.
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings(exampleCounter = 0)
override suspend fun readFrom(input: InputStream): Settings =
try {
Json.decodeFromString<Settings>(
input.readBytes().decodeToString()
)
} catch (serialization: SerializationException) {
throw CorruptionException("Unable to read Settings", serialization)
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
output.write(
Json.encodeToString(t)
.encodeToByteArray()
)
}
}
Proto DataStore
Proto DataStore uygulaması, türü belirlenmiş nesneleri diske kalıcı olarak kaydetmek için DataStore ve protokol arabelleklerini kullanır.
Proto DataStore, app/src/main/proto/ dizinindeki bir proto dosyasında önceden tanımlanmış bir şema gerektirir. Bu şema, Proto DataStore'unuzda kalıcı hale getirdiğiniz nesnelerin türünü tanımlar. Proto şeması tanımlama hakkında daha fazla bilgi edinmek için protobuf dil kılavuzuna bakın.
src/main/proto klasörüne settings.proto adlı bir dosya ekleyin:
syntax = "proto3";
option java_package = "com.example.datastore.snippets.proto";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Serializer<T> uygulayan bir sınıf tanımlayın. Burada T, proto dosyasında tanımlanan türdür. Bu serileştirici sınıfı, DataStore'un veri türünüzü nasıl okuyup yazdığını tanımlar. Henüz oluşturulmuş bir dosya yoksa kullanılacak seri hale getirici için varsayılan bir değer eklediğinizden emin olun.
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
return t.writeTo(output)
}
}
Veri deposu oluşturma
Verileri kalıcı hale getirmek için kullanılan dosyanın adını belirtmeniz gerekir.
Preferences DataStore
Preferences DataStore uygulaması, anahtar/değer çiftlerini diske kalıcı olarak kaydetmek için DataStore ve Preferences sınıflarını kullanır. DataStore<Preferences> örneği oluşturmak için preferencesDataStore tarafından oluşturulan özellik temsilcisini kullanın. Kotlin dosyanızın en üst düzeyinde bir kez çağırın. Uygulamanızın geri kalanında bu mülk aracılığıyla DataStore'a erişin. Bu sayede DataStore'unuzu tekil olarak tutmanız kolaylaşır.
Alternatif olarak, RxJava kullanıyorsanız RxPreferenceDataStoreBuilder'ı kullanın.
Zorunlu name parametresi, Preferences DataStore'un adıdır.
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
JSON DataStore
dataStore tarafından oluşturulan mülk temsilcisini kullanarak DataStore<T> örneği oluşturun. Burada T, serileştirilebilir veri sınıfıdır. Kotlin dosyanızın üst düzeyinde bir kez çağırın ve uygulamanızın geri kalanında bu temsilci özelliği aracılığıyla erişin. fileName parametresi, DataStore'a verileri depolamak için hangi dosyanın kullanılacağını, serializer parametresi ise 1. adımda tanımlanan serileştirici sınıfının adını bildirir.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.json",
serializer = SettingsSerializer,
)
Proto DataStore
dataStore tarafından oluşturulan mülk temsilcisini kullanarak DataStore<T> örneği oluşturun. Burada T, proto dosyasında tanımlanan türdür. Kotlin dosyanızın en üst düzeyinde bir kez çağırın ve uygulamanızın geri kalanında bu özellik temsilcisi aracılığıyla erişin. fileName parametresi, DataStore'a verileri depolamak için hangi dosyanın kullanılacağını, serializer parametresi ise 1. adımda tanımlanan serileştirici sınıfının adını bildirir.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer,
)
Veri deposundan okuma
Verileri kalıcı hale getirmek için kullanılan dosyanın adını belirtmeniz gerekir.
Preferences DataStore
Preferences DataStore önceden tanımlanmış bir şema kullanmadığından, DataStore<Preferences> örneğinde depolamanız gereken her değer için bir anahtar tanımlamak üzere ilgili anahtar türü işlevini kullanmanız gerekir. Örneğin, bir int değeri için anahtar tanımlamak üzere intPreferencesKey() kullanın. Ardından, bir Flow kullanarak uygun depolanmış değeri göstermek için DataStore.data özelliğini kullanın.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences ->
preferences[EXAMPLE_COUNTER] ?: 0
}
JSON DataStore
Depolanmış nesnenizden uygun özelliğin Flow değerini göstermek için DataStore.data kullanın.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
Proto DataStore
Depolanmış nesnenizden uygun özelliğin Flow değerini göstermek için DataStore.data kullanın.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
DataStore'a yazma
DataStore, depolanan bir nesneyi işlemsel olarak güncelleyen bir updateData() işlevi sağlar. updateData, verilerin mevcut durumunu veri türünüzün bir örneği olarak verir ve verileri atomik bir okuma-yazma-değiştirme işleminde işlemsel olarak günceller. updateData bloğundaki tüm kodlar tek bir işlem olarak değerlendirilir.
Preferences DataStore
suspend fun incrementCounter() {
context.dataStore.updateData {
it.toMutablePreferences().also { preferences ->
preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1
}
}
}
JSON DataStore
suspend fun incrementCounter() {
context.dataStore.updateData { settings ->
settings.copy(exampleCounter = settings.exampleCounter + 1)
}
}
Proto DataStore
suspend fun incrementCounter() {
context.dataStore.updateData { settings ->
settings.copy { exampleCounter = exampleCounter + 1 }
}
}
Oluşturma Örneği
Bu işlevleri bir sınıfta bir araya getirebilir ve Compose uygulamasında kullanabilirsiniz.
Preferences DataStore
Artık bu işlevleri PreferencesDataStore adlı bir sınıfa yerleştirebilir ve Compose uygulamasında kullanabiliriz.
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val preferencesDataStore = remember(context) { PreferencesDataStore(context) }
// Display counter value.
val exampleCounter by preferencesDataStore.counterFlow()
.collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
text = "Counter $exampleCounter",
fontSize = 25.sp
)
// Update the counter.
Button(
onClick = {
coroutineScope.launch { preferencesDataStore.incrementCounter() }
}
) {
Text("increment")
}
JSON DataStore
Artık bu işlevleri JSONDataStore adlı bir sınıfa yerleştirebilir ve Compose uygulamasında kullanabiliriz.
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val jsonDataStore = remember(context) { JsonDataStore(context) }
// Display counter value.
val exampleCounter by jsonDataStore.counterFlow()
.collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
text = "Counter $exampleCounter",
fontSize = 25.sp
)
// Update the counter.
Button(onClick = { coroutineScope.launch { jsonDataStore.incrementCounter() } }) {
Text("increment")
}
Proto DataStore
Artık bu işlevleri ProtoDataStore adlı bir sınıfa yerleştirebilir ve Compose uygulamasında kullanabiliriz.
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val protoDataStore = remember(context) { ProtoDataStore(context) }
// Display counter value.
val exampleCounter by protoDataStore.counterFlow()
.collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
text = "Counter $exampleCounter",
fontSize = 25.sp
)
// Update the counter.
Button(onClick = { coroutineScope.launch { protoDataStore.incrementCounter() } }) {
Text("increment")
}
DataStore'u eşzamanlı kodda kullanma
DataStore'un temel avantajlarından biri eşzamansız API'dir ancak çevreleyen kodunuzu eşzamansız olacak şekilde değiştirmek her zaman mümkün olmayabilir. Bu durum, eşzamanlı disk G/Ç'si kullanan mevcut bir kod tabanıyla çalışıyorsanız veya eşzamanlı olmayan bir API sağlamayan bir bağımlılığınız varsa geçerli olabilir.
Kotlin eş yordamları, senkron ve eşzamansız kod arasındaki boşluğu gidermenize yardımcı olmak için runBlocking() eş yordam oluşturucuyu sağlar. DataStore'dan verileri eşzamanlı olarak okumak için runBlocking() kullanabilirsiniz. RxJava, Flowable üzerinde engelleme yöntemleri sunar. Aşağıdaki kod, DataStore verileri döndürene kadar çağıran iş parçacığını engeller:
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
Kullanıcı arayüzü iş parçacığında eş zamanlı G/Ç işlemleri gerçekleştirmek ANR'lere veya yanıt vermeyen kullanıcı arayüzüne neden olabilir. Bu sorunları, DataStore'daki verileri eşzamansız olarak önceden yükleyerek azaltabilirsiniz:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
// You should also handle IOExceptions here.
}
}
Java
dataStore.data().first().subscribe();
Bu şekilde DataStore, verileri eşzamansız olarak okur ve bellekte önbelleğe alır. İlk okuma işlemi tamamlandıysa runBlocking() kullanılarak yapılan sonraki senkron okuma işlemleri daha hızlı olabilir veya disk G/Ç işlemi tamamen önlenebilir.
Çok işlemli kodda DataStore'u kullanma
DataStore'u, tek bir işlemdekiyle aynı veri tutarlılığı özellikleriyle farklı işlemler arasında aynı verilere erişecek şekilde yapılandırabilirsiniz. DataStore özellikle şunları sağlar:
- Okuma işlemleri yalnızca diske kalıcı olarak kaydedilmiş verileri döndürür.
- Yazma işleminden sonra okuma tutarlılığı.
- Yazma işlemleri sıralı olarak gerçekleştirilir.
- Okuma işlemleri hiçbir zaman yazma işlemleri tarafından engellenmez.
Hizmetin ayrı bir işlemde çalıştığı ve DataStore'u düzenli olarak güncellediği bir hizmet ve etkinlik içeren örnek bir uygulamayı ele alalım.
Bu örnekte JSON veri deposu kullanılmaktadır ancak tercihler veya proto veri deposu da kullanabilirsiniz.
@Serializable
data class Time(
val lastUpdateMillis: Long
)
Serileştirici, DataStore'ya veri türünüzü nasıl okuyup yazacağını söyler. Henüz oluşturulmuş bir dosya yoksa kullanılacak seri hale getirici için varsayılan bir değer eklediğinizden emin olun. Aşağıda, kotlinx.serialization kullanılarak yapılan bir örnek uygulama verilmiştir:
object TimeSerializer : Serializer<Time> {
override val defaultValue: Time = Time(lastUpdateMillis = 0L)
override suspend fun readFrom(input: InputStream): Time =
try {
Json.decodeFromString<Time>(
input.readBytes().decodeToString()
)
} catch (serialization: SerializationException) {
throw CorruptionException("Unable to read Time", serialization)
}
override suspend fun writeTo(t: Time, output: OutputStream) {
output.write(
Json.encodeToString(t)
.encodeToByteArray()
)
}
}
DataStore öğesini farklı işlemler genelinde kullanabilmek için hem uygulama hem de hizmet kodu için MultiProcessDataStoreFactory kullanarak DataStore nesnesini oluşturmanız gerekir:
val dataStore = MultiProcessDataStoreFactory.create(
serializer = TimeSerializer,
produceFile = {
File("${context.cacheDir.path}/time.pb")
},
corruptionHandler = null
)
AndroidManifiest.xml öğenize aşağıdakileri ekleyin:
<service
android:name=".TimestampUpdateService"
android:process=":my_process_id" />
Hizmet, updateLastUpdateTime() işlevini düzenli olarak çağırır. Bu işlev, updateData kullanarak veri deposuna yazar.
suspend fun updateLastUpdateTime() {
dataStore.updateData { time ->
time.copy(lastUpdateMillis = System.currentTimeMillis())
}
}
Uygulama, veri akışını kullanarak hizmet tarafından yazılan değeri okur:
fun timeFlow(): Flow<Long> = dataStore.data.map { time ->
time.lastUpdateMillis
}
Artık tüm bu işlevleri MultiProcessDataStore adlı bir sınıfta bir araya getirebilir ve bir uygulamada kullanabiliriz.
Hizmet kodu:
class TimestampUpdateService : Service() {
val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
val multiProcessDataStore by lazy { MultiProcessDataStore(applicationContext) }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
serviceScope.launch {
while (true) {
multiProcessDataStore.updateLastUpdateTime()
delay(1000)
}
}
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
serviceScope.cancel()
}
}
Uygulama kodu:
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val multiProcessDataStore = remember(context) { MultiProcessDataStore(context) }
// Display time written by other process.
val lastUpdateTime by multiProcessDataStore.timeFlow()
.collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
text = "Last updated: $lastUpdateTime",
fontSize = 25.sp
)
DisposableEffect(context) {
val serviceIntent = Intent(context, TimestampUpdateService::class.java)
context.startService(serviceIntent)
onDispose {
context.stopService(serviceIntent)
}
}
DataStore örneğinizin işlem başına benzersiz olması için Hilt bağımlılık eklemeyi kullanabilirsiniz:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
Dosya bozulmasıyla ilgili sorunları giderme
DataStore'un diskteki kalıcı dosyasının bozulabileceği nadir durumlar vardır. DataStore, varsayılan olarak bozulmadan otomatik olarak kurtulmaz ve DataStore'dan okuma girişimleri sistemin CorruptionException oluşturmasına neden olur.
DataStore, bu tür bir senaryoda sorunsuz bir şekilde kurtulmanıza ve istisna oluşturmaktan kaçınmanıza yardımcı olabilecek bir bozulma işleyici API'si sunar. Yapılandırıldığında, bozulma işleyici, bozuk dosyayı önceden tanımlanmış bir varsayılan değer içeren yeni bir dosya ile değiştirir.
Bu işleyiciyi ayarlamak için by dataStore() içinde veya DataStoreFactory fabrika yönteminde DataStore örneğini oluştururken corruptionHandler sağlayın:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
Geri bildirim gönder
Geri bildirimlerinizi ve fikirlerinizi aşağıdaki kaynaklar aracılığıyla bizimle paylaşabilirsiniz:
- Sorun izleyici:
- Hataları düzeltebilmemiz için sorunları bildirin.
Ek kaynaklar
Jetpack DataStore hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:
Örnekler
Bloglar
Codelab uygulamaları
Sizin için önerilenler
- Not: Bağlantı metni, JavaScript kapalıyken gösterilir.
- Sayfalandırılmış verileri yükleme ve görüntüleme
- LiveData'ya genel bakış
- Düzenler ve bağlama ifadeleri