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 tip 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.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-preferences-core:1.2.1" }
Kotlin
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation("androidx.datastore:datastore-preferences:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-preferences-core:1.2.1") }
İ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.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.1" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.1") }
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.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-core:1.2.1" }
Kotlin
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation("androidx.datastore:datastore:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-core:1.2.1") }
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.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.2.1" }
Kotlin
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.2.1") }
İç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 serileştirme 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> öğesinin bir örneğini 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.
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 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 daha önce 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> öğesinin bir örneğini oluşturun. Burada T, proto dosyasında tanımlanan türdür. Kotlin dosyanızın ü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 daha önce 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 akış 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
}
Bir ViewModel tarafından oluşturulan Flow öğesini composable'da kullanmak için collectAsStateWithLifecycle öğesini kullanın.
Bu, DataStore akışını güvenli bir şekilde yeniden oluşturmayı tetikleyen Compose durumuna dönüştürür.
@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
Text(text = "Example counter: ${counter}")
}
collectAsStateWithLifecycle hakkında daha fazla bilgi için Durum ve Jetpack Compose başlıklı makaleyi inceleyin.
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 }
}
}
Compose uygulamasında DataStore'u kullanma
Compose uygulamasında DataStore'u kullanmak için Android uygulama mimarisi yönergelerini uygulayarak DataStore işlemlerini veri katmanınızda (ör. bir depoda) tutun ve verileri ViewModel aracılığıyla kullanıcı arayüzünüze sunun.
Composable işlevlerinizde doğrudan DataStore'dan okuma veya DataStore'a yazma işlemlerinden kaçının.
DataStore'u bir ViewModel aracılığıyla kullanıma sunun. Deponuzu (DataStore'u sarmalayan)
ViewModeliçine aktarın veFlowöğesiniStateFlowöğesine dönüştürün. Böylece kullanıcı arayüzü, aşağıdaki snippet'te gösterildiği gibi kolayca gözlemleyebilir:class SettingsViewModel( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { // Expose the DataStore flow as a StateFlow for Compose val userSettings: StateFlow<UserSettings> = userPreferencesRepository.userSettingsFlow .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = UserSettings.getDefaultInstance() ) fun updateCounter(newValue: Int) { viewModelScope.launch { userPreferencesRepository.updateCounter(newValue) } } }Composable'ınızdan gözlem yapma ve yazma Kullanıcı arayüzünüzdeki
StateFlowöğesini güvenli bir şekilde gözlemlemek içincollectAsStateWithLifecycleöğesini kullanın ve aşağıdaki snippet'te gösterildiği gibi yazma işlemlerini gerçekleştirmek içinViewModelişlevlerini çağırın:@Composable fun SettingsScreen( viewModel: SettingsViewModel = viewModel() ) { // Safely collect the state val settings by viewModel.userSettings.collectAsStateWithLifecycle() Column(modifier = Modifier.padding(16.dp)) { Text(text = "Current counter: ${settings.counter}") Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { viewModel.updateCounter(settings.counter + 1) }) { Text("Increment Counter") } } }
DataStore'u çok işlemli kodda 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. Özellikle DataStore aşağıdaki özellikleri 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 DataStore kullanılmaktadır ancak Preferences veya Proto DataStore 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.filesDir.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ını ele alma
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.filesDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
Geri bildirim gönderme
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: JavaScript kapalıyken bağlantı metni gösterilir.
- Sayfalandırılmış verileri yükleme ve görüntüleme
- LiveData'ya genel bakış
- Düzenler ve bağlama ifadeleri