Project: /architecture/_project.yaml Book: /architecture/_book.yaml keywords: datastore, architecture, api:JetpackDataStore description: Jelajahi panduan arsitektur aplikasi ini tentang library lapisan data untuk mempelajari Preferensi DataStore dan Proto DataStore, Penyiapan, dan lainnya. hide_page_heading: true
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 Anda menggunakan SharedPreferences untuk menyimpan data, sebaiknya bermigrasilah ke DataStore.
DataStore API
Antarmuka DataStore menyediakan API berikut:
Alur yang dapat digunakan untuk membaca data dari DataStore
val data: Flow<T>Fungsi untuk memperbarui data di DataStore
suspend updateData(transform: suspend (t) -> T)
Konfigurasi DataStore
Jika Anda ingin menyimpan dan mengakses data menggunakan kunci, gunakan implementasi Preferences DataStore yang tidak memerlukan skema yang telah ditetapkan sebelumnya, dan tidak memberikan keamanan jenis. Memiliki API seperti SharedPreferences, tetapi tidak
memiliki kekurangan yang terkait dengan preferensi bersama.
DataStore memungkinkan Anda mempertahankan class kustom. Untuk melakukannya, Anda harus menentukan
skema untuk data dan menyediakan Serializer untuk mengonversinya ke format
yang dapat dipertahankan. Anda dapat memilih untuk menggunakan Protocol Buffers, JSON, atau strategi serialisasi lainnya.
Penyiapan
Untuk menggunakan Jetpack DataStore di aplikasi, tambahkan berikut ini ke file Gradle Anda bergantung pada implementasi yang ingin digunakan:
Preferences DataStore
Tambahkan baris berikut ke bagian dependensi file gradle Anda:
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") }
Untuk menambahkan dukungan RxJava opsional, tambahkan dependensi berikut:
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
Tambahkan baris berikut ke bagian dependensi file gradle Anda:
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") }
Tambahkan dependensi opsional berikut untuk dukungan RxJava:
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") }
Untuk melakukan serialisasi konten, tambahkan dependensi untuk serialisasi JSON atau Buffering Protokol.
Serialisasi JSON
Untuk menggunakan serialisasi JSON, tambahkan kode berikut ke file Gradle Anda:
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") }
Serialisasi Protobuf
Untuk menggunakan serialisasi Protobuf, tambahkan kode berikut ke file Gradle Anda:
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") } } } }
Menggunakan DataStore dengan benar
Agar dapat menggunakan DataStore dengan benar, selalu perhatikan aturan berikut:
Jangan pernah membuat lebih dari satu instance
DataStoreuntuk 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 menampilkanIllegalStateExceptionsaat membaca atau memperbarui data.Jenis generik
DataStore<T>harus tidak dapat diubah. Mengubah jenis yang digunakan di DataStore akan membatalkan konsistensi yang diberikan DataStore dan membuat bug serius yang berpotensi sulit ditemukan. Sebaiknya gunakan buffering protokol, yang membantu memastikan tidak dapat diubah, API yang jelas, dan serialisasi yang efisien.Jangan menggabungkan penggunaan
SingleProcessDataStoredanMultiProcessDataStoreuntuk file yang sama. Jika Anda ingin mengaksesDataStoredari lebih dari satu proses, Anda harus menggunakanMultiProcessDataStore.
Definisi Data
Preferences DataStore
Tentukan kunci yang akan digunakan untuk menyimpan data ke disk.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
JSON DataStore
Untuk penyimpanan data JSON, tambahkan anotasi @Serialization ke data yang ingin Anda pertahankan
@Serializable
data class Settings(
val exampleCounter: Int
)
Tentukan class yang mengimplementasikan Serializer<T>, dengan T adalah jenis
class yang Anda tambahkan anotasi sebelumnya. Pastikan Anda menyertakan nilai default untuk serialisasi yang akan digunakan jika belum ada file yang dibuat.
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
Implementasi Proto DataStore menggunakan DataStore dan buffering protokol untuk mempertahankan objek yang diketik ke disk.
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, lihat panduan bahasa protobuf.
Tambahkan file bernama settings.proto di dalam folder src/main/proto:
syntax = "proto3";
option java_package = "com.example.datastore.snippets.proto";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Tentukan class yang mengimplementasikan Serializer<T>, dengan T adalah jenis yang ditetapkan
dalam file proto. Class penserialisasi ini menentukan cara DataStore membaca dan
menulis jenis data Anda. Pastikan Anda menyertakan nilai default untuk
serializer yang akan digunakan jika belum ada file yang dibuat.
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)
}
}
Membuat DataStore
Anda harus menentukan nama untuk file yang digunakan untuk mempertahankan data.
Preferences DataStore
Implementasi Preferences DataStore menggunakan class DataStore dan
Preferences untuk mempertahankan key-value pair ke disk. Gunakan
delegasi properti yang dibuat oleh preferencesDataStore untuk membuat instance
DataStore<Preferences>. Panggil sekali di tingkat teratas file Kotlin Anda. Akses DataStore 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.
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
JSON DataStore
Gunakan delegasi properti yang dibuat oleh dataStore untuk membuat instance
DataStore<T>, dengan T adalah class data yang dapat diserialisasi. Panggil 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.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.json",
serializer = SettingsSerializer,
)
Proto DataStore
Gunakan delegasi properti yang dibuat oleh dataStore untuk membuat instance
DataStore<T>, dengan T adalah jenis yang ditetapkan dalam file proto. Panggil
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 penserialisasi yang ditentukan di langkah 1.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer,
)
Membaca dari DataStore
Anda harus menentukan nama untuk file yang digunakan untuk mempertahankan data.
Preferences DataStore
Karena Preferences DataStore tidak menggunakan skema yang telah ditetapkan sebelumnya, Anda harus menggunakan
fungsi jenis kunci yang sesuai untuk menentukan kunci bagi setiap nilai yang perlu
disimpan dalam instance DataStore<Preferences>. Misalnya, untuk menentukan
kunci nilai int, gunakan intPreferencesKey(). Kemudian, gunakan
properti DataStore.data untuk mengekspos nilai tersimpan yang sesuai menggunakan
Flow.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences ->
preferences[EXAMPLE_COUNTER] ?: 0
}
JSON DataStore
Gunakan DataStore.data untuk mengekspos Flow properti yang sesuai dari objek yang disimpan.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
Proto DataStore
Gunakan DataStore.data untuk mengekspos Flow properti yang sesuai dari objek yang disimpan.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
Menulis ke DataStore
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. Semua kode dalam blok updateData diperlakukan sebagai satu transaksi.
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 }
}
}
Contoh Compose
Anda dapat menggabungkan fungsi-fungsi ini dalam class dan menggunakannya di aplikasi Compose.
Preferences DataStore
Sekarang kita dapat memasukkan fungsi ini ke dalam class bernama PreferencesDataStore dan
menggunakannya di Aplikasi Compose.
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
Sekarang kita dapat memasukkan fungsi ini ke dalam class bernama JSONDataStore dan menggunakannya di Aplikasi Compose.
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
Sekarang kita dapat memasukkan fungsi ini ke dalam class bernama ProtoDataStore dan menggunakannya di Aplikasi Compose.
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")
}
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 UI tidak responsif. 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 dengan properti konsistensi data yang sama seperti dari dalam satu proses. Secara khusus, DataStore menyediakan:
- 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.
Pertimbangkan aplikasi contoh dengan layanan dan aktivitas tempat layanan berjalan dalam proses terpisah dan secara berkala memperbarui DataStore.
Contoh ini menggunakan datastore JSON, tetapi Anda juga dapat menggunakan datastore preferensi atau proto.
@Serializable
data class Time(
val lastUpdateMillis: Long
)
Serializer 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. Berikut adalah contoh penerapan menggunakan
kotlinx.serialization:
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()
)
}
}
Agar dapat menggunakan DataStore di berbagai proses, Anda harus membuat
objek DataStore menggunakan MultiProcessDataStoreFactory untuk aplikasi
dan kode layanan:
val dataStore = MultiProcessDataStoreFactory.create(
serializer = TimeSerializer,
produceFile = {
File("${context.cacheDir.path}/time.pb")
},
corruptionHandler = null
)
Tambahkan kode berikut ke AndroidManifiest.xml Anda:
<service
android:name=".TimestampUpdateService"
android:process=":my_process_id" />
Layanan ini secara berkala memanggil updateLastUpdateTime(), yang menulis ke
datastore menggunakan updateData.
suspend fun updateLastUpdateTime() {
dataStore.updateData { time ->
time.copy(lastUpdateMillis = System.currentTimeMillis())
}
}
Aplikasi membaca nilai yang ditulis oleh layanan menggunakan alur data:
fun timeFlow(): Flow<Long> = dataStore.data.map { time ->
time.lastUpdateMillis
}
Sekarang, kita dapat menggabungkan semua fungsi ini dalam class bernama
MultiProcessDataStore dan menggunakannya di Aplikasi.
Berikut kode layanannya:
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()
}
}
Dan kode aplikasi:
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)
}
}
Anda dapat menggunakan injeksi dependensi Hilt agar 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 memunculkan
CorruptionException.
DataStore menawarkan API handler kerusakan yang dapat membantu Anda memulihkan dengan baik dalam skenario tersebut, dan menghindari pengecualian. Jika dikonfigurasi, penangan kerusakan akan mengganti file yang rusak dengan file baru yang berisi nilai default yang telah ditentukan sebelumnya.
Untuk menyiapkan handler 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