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.
Menggunakan DataStore dengan benar
Agar dapat menggunakan DataStore dengan benar, selalu perhatikan aturan berikut:
Jangan pernah membuat lebih dari satu instance
DataStore
untuk file tertentu dengan proses yang sama. Tindakan ini dapat merusak semua fungsi DataStore. Jika ada beberapa DataStore yang aktif untuk file tertentu dalam proses yang sama, DataStore akan menampilkanIllegalStateException
saat membaca atau memperbarui data.Jenis DataStore umum
harus tidak dapat diubah. Mengubah jenis yang digunakan di DataStore akan membatalkan jaminan yang diberikan DataStore dan juga membuat bug serius yang berpotensi sulit ditemukan. Sebaiknya Anda menggunakan buffering protokol yang memberikan jaminan yang tidak dapat diubah, API yang sederhana, dan serialisasi yang efisien.Jangan pernah menggabungkan penggunaan
SingleProcessDataStore
danMultiProcessDataStore
untuk file yang sama. Jika Anda ingin mengaksesDataStore
dari beberapa proses, selalu gunakanMultiProcessDataStore
.
Penyiapan
Untuk menggunakan Jetpack DataStore di aplikasi, tambahkan berikut ini ke file Gradle Anda bergantung pada implementasi yang ingin digunakan:
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") }
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:
- Tentukan class yang mengimplementasikan
Serializer<T>
, denganT
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. - Gunakan delegasi properti yang dibuat oleh
dataStore
untuk membuat instanceDataStore<T>
, denganT
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. Parameterfilename
memberi tahu DataStore file mana yang akan digunakan untuk menyimpan data, dan parameterserializer
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.
Menggunakan DataStore dalam kode multiproses
Anda dapat mengonfigurasi DataStore untuk mengakses data yang sama di berbagai proses, dan konsistensi data terjamin tetap sama seperti berasal dari dalam satu proses. Secara khusus, DataStore menjamin:
- Operasi baca hanya menampilkan data yang telah disimpan ke disk.
- Konsistensi operasi baca setelah tulis.
- Penulisan diserialisasi.
- Operasi baca tidak pernah diblokir oleh operasi tulis.
Sebaiknya satu aplikasi contoh berisi satu layanan dan satu aktivitas:
Layanan berjalan dalam proses terpisah dan secara berkala memperbarui DataStore
<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) } } }
Aplikasi akan mengumpulkan perubahan tersebut, lalu mengupdate UI-nya
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Agar dapat menggunakan DataStore di berbagai proses, Anda harus membuat
objek DataStore menggunakan MultiProcessDataStoreFactory
.
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
akan memberi tahu DataStore cara membaca dan menulis jenis data.
Pastikan Anda menyertakan nilai default untuk serialisasi yang akan digunakan jika
belum ada file yang dibuat. Berikut adalah contoh implementasi yang menggunakan
kotlinx.serialization:
@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()
)
}
}
Anda dapat menggunakan injeksi dependensi Hilt untuk memastikan instance DataStore Anda bersifat unik untuk setiap proses:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
Menangani kerusakan file
Terkadang, file persisten di disk DataStore dapat
rusak. Secara default, DataStore tidak otomatis pulih dari kerusakan,
dan upaya untuk membaca darinya akan menyebabkan sistem menampilkan
CorruptionException
.
DataStore menawarkan API pengendali kerusakan yang dapat membantu Anda memulihkan dengan baik dalam skenario tersebut, dan menghindari pengecualian. Saat dikonfigurasi, pengendali kerusakan akan mengganti file yang rusak dengan file baru yang berisi nilai default yang telah ditentukan sebelumnya.
Untuk menyiapkan pengendali ini, berikan corruptionHandler
saat membuat instance DataStore di by dataStore()
atau dalam metode factory DataStoreFactory
:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
Berikan 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:
Contoh
Blog
Codelab
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Memuat dan menampilkan data yang dibagi-bagi
- Ringkasan LiveData
- Tata letak dan ekspresi binding