Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

DataStore   Bagian dari Android Jetpack.

Jetpack DataStore adalah solusi penyimpanan data yang memungkinkan Anda menyimpan key-value pair atau objek yang diketik dengan buffering protokol. DataStore menggunakan coroutine Kotlin dan Flow untuk menyimpan data secara asinkron, konsisten, dan transaksional.

Jika saat ini Anda menggunakan SharedPreferences untuk menyimpan data, sebaiknya bermigrasilah ke DataStore.

Preferences DataStore dan Proto DataStore

DataStore menyediakan dua implementasi yang berbeda: Preferences DataStore dan Proto DataStore.

  • Preferences DataStore menyimpan dan mengakses data menggunakan kunci. Implementasi ini tidak memerlukan skema yang telah ditetapkan sebelumnya, dan tidak memberikan keamanan jenis.
  • Proto DataStore menyimpan data sebagai instance jenis data kustom. Implementasi ini mengharuskan Anda untuk menentukan skema menggunakan buffering protokol, tetapi memberikan keamanan jenis.

Penyiapan

Untuk menggunakan Jetpack DataStore di aplikasi, tambahkan berikut ini ke file Gradle Anda bergantung pada implementasi yang ingin digunakan:

Berjenis

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

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

  // optional - RxJava3 support
  implementation "androidx.datastore:datastore-rxjava3:1.0.0-alpha08"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
  implementation "androidx.datastore:datastore-core:1.0.0-alpha08"
}

Preferensi

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

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

  // optional - RxJava3 support
  implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0-alpha08"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
  implementation "androidx.datastore:datastore-preferences-core:1.0.0-alpha08"
}

Menyimpan key-value pair dengan Preferences DataStore

Implementasi Preferences DataStore menggunakan class DataStore dan Preferences untuk mempertahankan key-value pair sederhana ke disk.

Membuat Preferences DataStore

Gunakan delegasi properti yang dibuat oleh preferencesDataStore untuk membuat instance Datastore<Preferences>. Panggil sekali di tingkat teratas file kotlin, dan akses melalui properti ini di seluruh aplikasi Anda. Hal ini memudahkan Anda mempertahankan DataStore sebagai singleton. Atau, gunakan RxPreferenceDataStoreBuilder jika Anda menggunakan RxJava. Parameter name wajib adalah nama Preferences DataStore.

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();

Membaca dari Preferences DataStore

Karena Preferences DataStore tidak menggunakan skema yang telah ditetapkan sebelumnya, Anda harus menggunakan fungsi jenis kunci yang sesuai untuk menentukan kunci untuk setiap nilai yang perlu disimpan dalam instance DataStore<Preferences>. Misalnya, untuk menentukan kunci nilai int, gunakan intPreferencesKey(). Selanjutnya, gunakan properti DataStore.data untuk mengekspos nilai tersimpan yang sesuai menggunakan Flow.

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));

Menulis ke Preferences DataStore

Preferences DataStore menyediakan fungsi edit() yang mengupdate data secara transaksional dalam DataStore. Parameter transform fungsi menerima blok kode tempat Anda dapat mengupdate nilai yang diperlukan. Semua kode dalam blok transformasi diperlakukan sebagai transaksi tunggal.

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.

Menyimpan objek yang diketik dengan Proto DataStore

Implementasi Proto DataStore menggunakan DataStore dan buffering protokol untuk mempertahankan objek yang diketik ke disk.

Menetapkan skema

Proto DataStore memerlukan skema yang telah ditetapkan sebelumnya dalam file proto di direktori app/src/main/proto/. Skema ini menetapkan jenis objek yang Anda pertahankan di Proto DataStore. Untuk mempelajari lebih lanjut penetapan skema proto, baca panduan bahasa protobuf.

syntax = "proto3";

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

message Settings {
  int32 example_counter = 1;
}

Membuat Proto DataStore

Ada dua langkah diperlukan untuk membuat Proto DataStore untuk menyimpan objek yang diketik:

  1. Tentukan class yang mengimplementasikan Serializer<T>, dengan T adalah jenis yang ditetapkan dalam file proto. Class penserialisasi ini memberi tahu DataStore cara membaca dan menulis jenis data Anda. Pastikan Anda menyertakan nilai default untuk serialisasi yang akan digunakan jika belum ada file yang dibuat.
  2. Gunakan delegasi properti yang dibuat oleh dataStore untuk membuat instance DataStore<T>, dengan T adalah jenis yang ditetapkan dalam file proto. Panggil ini sekali di tingkat teratas file kotlin dan akses melalui delegasi properti ini di seluruh aplikasi Anda. Parameter filename memberi tahu DataStore file mana yang akan digunakan untuk menyimpan data, dan parameter serializer memberi tahu DataStore nama class serialisasi yang ditentukan di langkah 1.

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();

Membaca dari Proto DataStore

Gunakan DataStore.data untuk mengekspos Flow properti yang sesuai dari objek yang disimpan.

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());

Menulis ke Proto DataStore

Proto DataStore menyediakan fungsi updateData() yang mengupdate objek yang disimpan secara transaksional. updateData() memberi Anda status data saat ini sebagai instance jenis data dan mengupdate data secara transaksional dalam operasi baca-tulis-modifikasi atomik.

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()));

Menggunakan DataStore dalam kode sinkron

Salah satu manfaat utama DataStore adalah API asinkron, tetapi tidak selalu memungkinkan untuk mengubah kode di sekitarnya menjadi asinkron. Hal ini mungkin terjadi jika Anda menangani codebase yang ada yang menggunakan I/O disk sinkron atau jika Anda memiliki dependensi yang tidak menyediakan API asinkron.

Coroutine Kotlin menyediakan builder coroutine runBlocking() untuk membantu menjembatani jarak antara kode sinkron dan asinkron. Anda dapat menggunakan runBlocking() untuk membaca data dari DataStore secara sinkron. RxJava menawarkan metode pemblokiran di Flowable. Kode berikut memblokir thread panggilan hingga DataStore menampilkan data:

Kotlin

val exampleData = runBlocking { context.dataStore.data.first() }

Java

Settings settings = dataStore.data().blockingFirst();

Menjalankan operasi I/O sinkron pada UI thread dapat menyebabkan ANR atau jank UI. Anda dapat mencegah masalah ini dengan melakukan pramuat data secara asinkron dari DataStore:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

Java

dataStore.data().first().subscribe();

Dengan demikian, DataStore secara asinkron membaca data dan meng-cache-nya dalam memori. Selanjutnya, pembacaan sinkron menggunakan runBlocking() dapat lebih cepat atau menghindari operasi I/O disk sepenuhnya jika pembacaan awal telah selesai.

Memberikan masukan

Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:

Issue tracker
Laporkan masalah agar kami dapat memperbaiki bug.

Referensi lainnya

Untuk mempelajari Jetpack DataStore lebih lanjut, lihat referensi tambahan berikut:

Blog

Codelab