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.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") }
Untuk menambahkan dukungan RxJava opsional, tambahkan dependensi berikut:
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
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.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") }
Tambahkan dependensi opsional berikut untuk dukungan RxJava:
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") }
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.
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 Anda 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 sebelumnya.
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 Anda 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 sebelumnya.
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
}
Gunakan collectAsStateWithLifecycle untuk menggunakan Flow yang dihasilkan oleh
ViewModel dalam composable.
Hal ini mengonversi Alur DataStore dengan aman menjadi Status Compose yang memicu
rekomposisi.
@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
Text(text = "Example counter: ${counter}")
}
Untuk mengetahui informasi selengkapnya tentang collectAsStateWithLifecycle,
lihat Status dan Jetpack Compose.
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 }
}
}
Menggunakan DataStore di aplikasi Compose
Untuk menggunakan DataStore di aplikasi Compose, ikuti panduan arsitektur aplikasi Android dengan
mempertahankan operasi DataStore di lapisan data (seperti repositori) dan
mengekspos data ke UI melalui ViewModel.
Hindari membaca dari atau menulis ke DataStore secara langsung dalam fungsi composable Anda.
Mengekspos DataStore melalui ViewModel. Teruskan repositori Anda (yang membungkus DataStore) ke
ViewModeldan konversiFlowkeStateFlowsehingga UI dapat mengamatinya dengan mudah, seperti yang ditunjukkan dalam cuplikan berikut: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) } } }Amati dan tulis dari composable Anda. Gunakan
collectAsStateWithLifecycleuntuk mengamatiStateFlowdengan aman di UI Anda, dan panggil fungsiViewModeluntuk menangani penulisan, seperti yang ditunjukkan dalam cuplikan berikut:@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") } } }
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 properti berikut:
- 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 Preferences atau Proto DataStore.
@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 kode aplikasi
dan layanan:
val dataStore = MultiProcessDataStoreFactory.create(
serializer = TimeSerializer,
produceFile = {
File("${context.filesDir.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.filesDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
Berikan masukan
Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:
- Pelacak masalah:
- 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