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. API ini memiliki API seperti SharedPreferences, tetapi tidak
memiliki kelemahan 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 Buffering Protokol, 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
DataStore<T>umum harus tidak dapat diubah. Mengubah jenis yang digunakan di DataStore akan membatalkan konsistensi yang diberikan DataStore dan juga membuat bug serius yang berpotensi sulit ditemukan. Sebaiknya gunakan buffering protokol, yang membantu memastikan immutabilitas, API yang jelas, dan serialisasi yang efisien.Jangan mencampur penggunaan
SingleProcessDataStoredanMultiProcessDataStoreuntuk file yang sama. Jika Anda ingin mengaksesDataStoredari beberapa proses, Anda harus menggunakanMultiProcessDataStore.
Definisi Data
Preferences DataStore
Tentukan kunci yang akan digunakan untuk mempertahankan data ke disk.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
JSON DataStore
Untuk JSON datastore, 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, baca 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 serialisasi 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 perlu 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 serialisasi 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 serialisasi yang ditentukan sebelumnya.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer,
)
Membaca dari DataStore
Anda perlu 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 untuk setiap nilai yang Anda
perlu disimpan dalam instance DataStore<Preferences>. Misalnya, untuk menentukan
kunci untuk nilai int, gunakan intPreferencesKey. Selanjutnya, 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.
Tindakan ini akan mengonversi DataStore Flow dengan aman menjadi Compose State 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 transaksi tunggal.
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 menyimpan operasi DataStore di lapisan data Anda (seperti repositori) dan mengekspos data ke UI Anda melalui ViewModel.
Hindari membaca dari atau menulis ke DataStore secara langsung dalam fungsi composable Anda.
Ekspos DataStore melalui ViewModel. Teruskan repositori Anda (yang menggabungkan 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, dan properti konsistensi data terjamin tetap sama seperti berasal 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.
Sebaiknya satu aplikasi contoh berisi satu layanan dan satu aktivitas, dengan layanan berjalan dalam proses terpisah dan secara berkala memperbarui DataStore.
Contoh ini menggunakan JSON datastore, 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 implementasi yang 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 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 aliran 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 layanan:
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 Hilt injeksi dependensi sehingga 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 DataStore di disk dapat rusak. Secara default, DataStore tidak otomatis pulih dari kerusakan, dan upaya untuk membacanya akan menyebabkan sistem menampilkan CorruptionException.
DataStore menawarkan corruption handler API yang dapat membantu Anda pulih dengan lancar dalam skenario seperti itu, dan menghindari menampilkan pengecualian. Jika dikonfigurasi, corruption handler akan mengganti file yang rusak dengan file baru yang berisi nilai default yang telah ditetapkan sebelumnya.
Untuk menyiapkan handler ini, berikan corruptionHandler saat membuat instance DataStore di by dataStore atau di 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