DataStore   Android Jetpack'in bir parçasıdır.

Jetpack DataStore, protokol arabellekleriyle anahtar/değer çiftlerini veya türlenmiş 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.

Veri depolamak için şu anda SharedPreferences kullanıyorsanız bunun yerine DataStore'a geçiş yapabilirsiniz.

Tercihler Veri Deposu ve Proto Veri Deposu

DataStore iki farklı uygulama sağlar: Preferences DataStore ve Proto DataStore.

  • Tercihler Veri Deposu, verileri anahtarları kullanarak depolar ve verilere erişir. Bu uygulama, önceden tanımlanmış bir şema gerektirmez ve tür güvenliği sağlamaz.
  • Proto DataStore, verileri özel veri türünün örnekleri olarak depolar. Bu uygulama, protokol arabelleklerini kullanarak bir şema tanımlamanızı gerektirir ancak tür güvenliği sağlar.

DataStore'u doğru kullanma

DataStore'u doğru şekilde kullanmak için aşağıdaki kuralları her zaman göz önünde bulundurun:

  1. 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şlevlerini bozabilir. Aynı işlemde belirli bir dosya için etkin birden fazla DataStore varsa DataStore, verileri okurken veya güncellerken IllegalStateException hatası verir.

  2. Veri deposunun genel türü sabit olmalıdır. DataStore'da kullanılan bir türün değiştirilmesi, DataStore'un sağladığı tüm garantileri geçersiz kılar ve potansiyel olarak ciddi ve yakalanması zor hatalar oluşturur. Değişmezlik garantisi, basit bir API ve verimli serileştirme sağlayan protokol arabellekleri kullanmanız önemle tavsiye edilir.

  3. Aynı dosya için SingleProcessDataStore ve MultiProcessDataStore kullanımlarını hiçbir zaman karıştırmayın. DataStore öğesine birden fazla işlemden erişmeyi planlıyorsanız her zaman MultiProcessDataStore kullanın.

Kurulum

Jetpack DataStore'u uygulamanızda kullanmak için kullanmak istediğiniz uygulamaya bağlı olarak Gradle dosyanıza aşağıdakileri ekleyin:

Preferences DataStore

Groovy

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation "androidx.datastore:datastore-preferences:1.1.1"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.1"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.1.1"
    }
    

Kotlin

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation("androidx.datastore:datastore-preferences:1.1.1")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.1.1")
    }
    

Proto DataStore

Groovy

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.1.1"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.1.1"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.1.1"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-core:1.1.1"
    }
    

Kotlin

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.1.1")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.1.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.1.1")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.1.1")
    }
    

Anahtar/değer çiftlerini Preferences DataStore ile depolama

Tercihler Datastore uygulaması, basit anahtar/değer çiftlerini diskte sürdürmek için DataStore ve Preferences sınıflarını kullanır.

Tercihler Veri Deposu Oluşturma

DataStore<Preferences> örneği oluşturmak için preferencesDataStore tarafından oluşturulan mülk temsilcisini kullanın. Kotlin dosyanızın en üst düzeyinde bir kez çağırın ve uygulamanızın geri kalanında bu mülk aracılığıyla erişin. Bu sayede DataStore öğenizi tekil olarak tutmanız kolaylaşır. Alternatif olarak, RxJava kullanıyorsanız RxPreferenceDataStoreBuilder değerini kullanın. Zorunlu name parametresi, Tercihler Veri Deposu'nun adıdır.

Kotlin

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Java

RxDataStore<Preferences> dataStore =
  new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();

Tercihler Veri Deposu'ndan veri okuma

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 istiyorsanız intPreferencesKey() değerini kullanın. Ardından, Flow kullanarak uygun depolanan değeri göstermek için DataStore.data özelliğini kullanın.

Kotlin

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

Java

Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));

Tercihler Veri Deposu'na yazma

Tercihler Veri Deposu, DataStore içindeki verileri işlemsel olarak güncelleyen bir edit() işlevi sağlar. İşlevin transform parametresi, değerleri gerektiği gibi güncelleyebileceğiniz bir kod bloğu kabul eder. Dönüşüm bloğundaki tüm kod tek bir işlem olarak değerlendirilir.

Kotlin

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

Java

Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
  MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
  Integer currentInt = prefsIn.get(INTEGER_KEY);
  mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
  return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.

Proto DataStore ile tescilli nesneleri depolama

Proto DataStore uygulaması, tescilli nesneleri diskte sürdürmek için DataStore ve protokol arabelleklerini kullanır.

Şema tanımlama

Proto DataStore, app/src/main/proto/ dizininde bir proto dosyasında önceden tanımlanmış bir şema gerektirir. Bu şema, Proto DataStore'unuzda kalıcı olarak depolanan nesnelerin türünü tanımlar. Proto şeması tanımlama hakkında daha fazla bilgi edinmek için protobuf dil kılavuzuna bakın.

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

Proto Veri Deposu oluşturma

Yazılım nesnelerinizi depolamak için Proto DataStore oluşturmanın iki adımı vardır:

  1. Serializer<T> sınıfını 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'a veri türünüzü nasıl okuyacağını ve yazacağını söyler. Henüz oluşturulmuş bir dosya yoksa serileştiricinin kullanacağı varsayılan bir değer eklediğinizden emin olun.
  2. 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. Bu işlevi kotlin dosyanızın üst düzeyinde bir kez çağırın ve uygulamanızın geri kalanında bu mülk temsilcisi aracılığıyla erişin. filename parametresi, verileri depolamak için hangi dosyanın kullanılacağını DataStore'a bildirir. serializer parametresi ise 1. adımda tanımlanan serileştirici sınıfının adını DataStore'a bildirir.

Kotlin

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) = t.writeTo(output)
}

val Context.settingsDataStore: DataStore<Settings> by dataStore(
  fileName = "settings.pb",
  serializer = SettingsSerializer
)

Java

private static class SettingsSerializer implements Serializer<Settings> {
  @Override
  public Settings getDefaultValue() {
    Settings.getDefaultInstance();
  }

  @Override
  public Settings readFrom(@NotNull InputStream input) {
    try {
      return Settings.parseFrom(input);
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException(“Cannot read proto.”, exception);
    }
  }

  @Override
  public void writeTo(Settings t, @NotNull OutputStream output) {
    t.writeTo(output);
  }
}

RxDataStore<Byte> dataStore =
    new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();

Proto Datastore'tan veri okuma

Depolanan nesnenizdeki uygun mülkün Flow değerini göstermek için DataStore.data değerini kullanın.

Kotlin

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

Java

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(settings -> settings.getExampleCounter());

Proto DataStore'a yazma

Proto 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 gösterir ve verileri atomik bir okuma-yazma-değiştirme işleminde işlemsel olarak günceller.

Kotlin

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

Java

Single<Settings> updateResult =
  dataStore.updateDataAsync(currentSettings ->
    Single.just(
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.getExampleCounter() + 1)
        .build()));

Eşzamanlı kodda DataStore'u kullanma

DataStore'un başlıca avantajlarından biri eşzamansız API'dir ancak çevrenizdeki kodu eşzamansız olacak şekilde değiştirmek her zaman mümkün olmayabilir. Bu durum, senkronize disk I/O kullanan mevcut bir kod tabanıyla çalışıyorsanız veya asenkron API sağlamayan bir bağımlılığınız varsa söz konusu olabilir.

Kotlin coroutine'leri, senkron ve asenkron kod arasındaki boşluğu doldurmaya yardımcı olmak için runBlocking() coroutine oluşturucusunu sağlar. DataStore'daki verileri senkronize olarak okumak için runBlocking() işlevini kullanabilirsiniz. RxJava, Flowable'te engelleme yöntemleri sunar. Aşağıdaki kod, DataStore veri 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 senkronize G/Ç işlemleri gerçekleştirmek ANR'lere veya kullanıcı arayüzünde takılmalara neden olabilir. Verileri DataStore'dan eşzamansız olarak önceden yükleyerek bu sorunları 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 sayede DataStore, verileri ayarlanan aralıkta senkronize olmayan bir şekilde okur ve bellekte önbelleğe alır. İlk okuma tamamlandıysa runBlocking() kullanan sonraki senkronize okumalar daha hızlı olabilir veya disk I/O işlemini tamamen önleyebilir.

Çok işlemli kodda DataStore'u kullanma

DataStore'u, tek bir işlemdekiyle aynı veri tutarlılık garantileriyle farklı işlemlerde aynı verilere erişecek şekilde yapılandırabilirsiniz. Veri deposu özellikle aşağıdakileri garanti eder:

  • Okuma işlemleri yalnızca diske kaydedilmiş verileri döndürür.
  • Yazma işleminden sonra okuma tutarlılığı.
  • Yazmalar serileştirilir.
  • Okuma işlemleri hiçbir zaman yazma işlemleri tarafından engellenmez.

Bir hizmet ve etkinlik içeren örnek bir uygulamayı ele alalım:

  1. Hizmet ayrı bir işlemde çalışır ve DataStore'u düzenli olarak günceller.

    <service
      android:name=".MyService"
      android:process=":my_process_id" />
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          scope.launch {
              while(isActive) {
                  dataStore.updateData {
                      Settings(lastUpdate = System.currentTimeMillis())
                  }
                  delay(1000)
              }
          }
    }
    
  2. Uygulama bu değişiklikleri toplayıp kullanıcı arayüzünü günceller.

    val settings: Settings by dataStore.data.collectAsState()
    Text(
      text = "Last updated: $${settings.timestamp}",
    )
    

DataStore'u farklı işlemlerde kullanabilmek için MultiProcessDataStoreFactory kullanarak DataStore nesnesini oluşturmanız gerekir.

val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
   serializer = SettingsSerializer(),
   produceFile = {
       File("${context.cacheDir.path}/myapp.preferences_pb")
   }
)

serializer, DataStore'a veri türünüzü nasıl okuyacağını ve yazacağını söyler. Henüz oluşturulmuş bir dosya yoksa serileştiricinin kullanacağı varsayılan bir değer eklediğinizden emin olun. Aşağıda, kotlinx.serialization kullanan örnek bir uygulama verilmiştir:

@Serializable
data class Settings(
   val lastUpdate: Long
)

@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {

   override val defaultValue = Settings(lastUpdate = 0)

   override suspend fun readFrom(input: InputStream): Timer =
       try {
           Json.decodeFromString(
               Settings.serializer(), 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(Settings.serializer(), t)
               .encodeToByteArray()
       )
   }
}

DataStore örneğinizin işlem başına benzersiz olmasını sağlamak için Hilt bağımlılık ekleme özelliğini kullanabilirsiniz:

@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
   MultiProcessDataStoreFactory.create(...)

Dosya bozulmasını ele alma

DataStore'ın kalıcı disk üzerindeki dosyasının bozulabileceği nadir durumlar vardır. Varsayılan olarak DataStore, bozulmayı otomatik olarak kurtarmaz ve ondan veri okuma girişimleri sistemin CorruptionException hatası vermesine neden olur.

DataStore, böyle bir senaryoda sorunsuz bir şekilde kurtarmanıza ve istisna atmamanıza yardımcı olabilecek bir bozulma işleyici API'si sunar. Yapılandırma yapıldığında bozulma işleyici, bozuk dosyayı önceden tanımlanmış bir varsayılan değer içeren yeni bir dosyayla değiştirir.

Bu işleyiciyi ayarlamak için by dataStore() içinde veya DataStoreFactory fabrika yönteminde DataStore örneğini oluştururken bir 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 kaynaklardan 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ı