1. Pengantar
Apa itu DataStore?
DataStore adalah solusi penyimpanan data yang baru dan lebih baik yang ditujukan untuk menggantikan SharedPreferences. Dibuat di coroutine Kotlin dan Flow, DataStore menyediakan dua implementasi berbeda: Proto DataStore yang menyimpan objek berjenis (didukung oleh buffering protokol) dan Preferences DataStore yang menyimpan key-value pair. Data disimpan secara asinkron, konsisten, dan transaksional, yang mengatasi beberapa kelemahan SharedPreferences.
Yang akan Anda pelajari
- Pengertian DataStore dan alasan harus menggunakannya.
- Cara menambahkan DataStore ke project Anda.
- Perbedaan antara Preferences DataStore dan Proto DataStore, serta keunggulan keduanya.
- Cara menggunakan Preferences DataStore.
- Cara bermigrasi dari SharedPreferences ke Preferences DataStore.
Yang akan Anda buat
Dalam codelab ini, Anda akan memulai dengan aplikasi contoh yang menampilkan daftar tugas yang dapat difilter menurut status selesainya, serta dapat diurutkan menurut prioritas dan batas waktu.
Flag boolean untuk filter Show completed tasks disimpan di memori. Tata urutan dipertahankan ke disk menggunakan objek SharedPreferences
.
Dalam codelab ini, Anda akan mempelajari cara menggunakan Preferences DataStore dengan menyelesaikan tugas berikut:
- Mempertahankan filter status selesai di DataStore.
- Memigrasikan tata urutan dari SharedPreferences ke DataStore.
Sebaiknya gunakan juga codelab Proto DataStore agar Anda dapat lebih memahami perbedaan antara keduanya.
Yang akan Anda butuhkan
- Android Studio Arctic Fox.
- Pemahaman tentang Komponen Arsitektur berikut: LiveData, ViewModel, View Binding, dan dengan arsitektur yang disarankan di bagian Panduan arsitektur aplikasi.
- Pemahaman tentang coroutine dan Flow Kotlin.
Untuk pengantar Komponen Arsitektur, lihat Room dengan codelab View. Untuk pengantar Flow, lihat Coroutine Lanjutan dengan Flow Kotlin dan codelab LiveData.
2. Mempersiapkan
Pada langkah ini, Anda akan mendownload kode untuk seluruh codelab kemudian menjalankan aplikasi contoh sederhana.
Untuk memulainya secepat mungkin, kami telah menyiapkan project awal untuk Anda kembangkan.
Jika sudah menginstal git, Anda cukup menjalankan perintah di bawah. Untuk memeriksa apakah git sudah diinstal, ketik git --version
di terminal atau command line dan pastikan git dijalankan dengan benar.
git clone https://github.com/googlecodelabs/android-datastore
Status awal berada di cabang master
. Kode solusi terletak di cabang preferences_datastore
.
Jika tidak memiliki git, Anda dapat mengklik tombol berikut untuk mendownload semua kode untuk codelab ini:
- Buka zip kode, lalu buka project di Android Studio Arctic Fox.
- Jalankan aplikasi yang menjalankan konfigurasi di perangkat atau emulator.
Aplikasi mulai berjalan dan menampilkan daftar tugas:
3. Ringkasan project
Aplikasi akan memungkinkan Anda melihat daftar tugas. Setiap tugas memiliki properti berikut: nama, status selesai, prioritas, dan batas waktu.
Untuk menyederhanakan kode yang perlu ditangani, aplikasi hanya mengizinkan Anda melakukan dua tindakan:
- Mengubah visibilitas Show completed tasks - tugas disembunyikan secara default
- Mengurutkan tugas menurut prioritas, batas waktu, atau batas waktu dan prioritas
Aplikasi mengikuti arsitektur yang direkomendasikan di Panduan arsitektur aplikasi. Anda akan menemukan item berikut di setiap paket:
data
- Class model
Task
. - Class
TasksRepository
- bertanggung jawab untuk menyediakan tugas. Untuk alasan kemudahan, class ini menampilkan data hardcode dan mengeksposnya melaluiFlow
untuk merepresentasikan skenario yang lebih realistis. - Class
UserPreferencesRepository
- menampungSortOrder
yang didefinisikan sebagaienum
. Tata urutan saat ini disimpan di SharedPreferences sebagaiString
, berdasarkan nama nilai enum. Ini akan memperlihatkan metode sinkron untuk menyimpan dan mendapatkan tata urutan.
ui
- Class yang terkait menampilkan
Activity
denganRecyclerView
. - Class
TasksViewModel
bertanggung jawab atas logika UI.
TasksViewModel
- menampung semua elemen yang diperlukan untuk mem-build data yang perlu ditampilkan di UI: daftar tugas, flag showCompleted
dan sortOrder
, yang digabungkan dalam objek TasksUiModel
. Setiap kali salah satu nilai ini berubah, TasksUiModel
yang baru harus direkonstruksi. Untuk dapat melakukannya, 3 elemen akan digabungkan:
Flow<List<Task>>
yang diambil dariTasksRepository
.MutableStateFlow<Boolean>
yang menyimpan flagshowCompleted
terbaru, yang hanya disimpan dalam memori.MutableStateFlow<SortOrder>
yang memiliki nilaisortOrder
terakhir.
Untuk memastikan bahwa UI telah diperbarui dengan benar, LiveData<TasksUiModel>
akan diekspos hanya saat Aktivitas dimulai.
Ada beberapa masalah dengan kode:
- Kita memblokir UI thread pada I/O disk saat menginisialisasi
UserPreferencesRepository.sortOrder
. Hal ini dapat mengakibatkan jank pada UI. - Flag
showCompleted
hanya disimpan dalam memori, sehingga reset akan terjadi setiap kali pengguna membuka aplikasi. SepertisortOrder
, flag ini harus disimpan agar tetap ada saat aplikasi ditutup. - Saat ini kita menggunakan SharedPreferences untuk mempertahankan data, tetapi menyimpan
MutableStateFlow
dalam memori yang telah dimodifikasi secara manual agar dapat menerima notifikasi perubahan. Cara ini cenderung mudah mengalami gangguan jika nilai diubah di tempat lain di aplikasi. - Di
UserPreferencesRepository
, kita mengekspos dua metode untuk memperbarui tata urutan:enableSortByDeadline()
danenableSortByPriority()
. Kedua metode tersebut bergantung pada nilai tata urutan saat ini. Namun, jika salah satu metode dipanggil sebelum metode lainnya selesai, nilai akhir akan salah. Bahkan, metode ini dapat mengakibatkan jank pada UI dan pelanggaran Mode Ketat saat metode dipanggil di UI thread.
Mari cari tahu cara menggunakan DataStore untuk membantu menangani masalah ini.
4. DataStore - dasar-dasar
Sering kali Anda mungkin perlu menyimpan set data kecil atau sederhana. Sebelumnya, Anda mungkin telah menggunakan SharedPreferences, tetapi API ini juga memiliki serangkaian kelemahan. Library Jetpack DataStore bertujuan mengatasi masalah tersebut dan membuat API yang sederhana, aman, dan asinkron untuk menyimpan data. Library tersebut menyediakan 2 implementasi yang berbeda:
- Preferences DataStore
- Proto DataStore
Fitur | SharedPreferences | PreferencesDataStore | ProtoDataStore |
API asinkron | ✅ (hanya untuk membaca nilai yang diubah, melalui pemroses) | ✅ (melalui | ✅ (melalui |
API sinkron | ✅ (tetapi tidak aman untuk dipanggil di UI thread) | ❌ | ❌ |
Aman untuk dipanggil di UI thread | ❌1 | ✅ (tugas dipindahkan ke | ✅ (tugas dipindahkan ke |
Dapat memperingatkan adanya error | ❌ | ✅ | ✅ |
Aman dari pengecualian runtime | ❌2 | ✅ | ✅ |
Memiliki API transaksional dengan jaminan konsistensi kuat | ❌ | ✅ | ✅ |
Menangani migrasi data | ❌ | ✅ | ✅ |
Keamanan jenis | ❌ | ❌ | ✅ dengan Buffering Protokol |
1 SharedPreferences memiliki API sinkron yang dapat terlihat aman untuk dipanggil di UI thread, tetapi sebenarnya melakukan operasi I/O disk. Selanjutnya, apply()
akan memblokir UI thread di fsync()
. Panggilan fsync()
yang tertunda dipicu setiap kali layanan dimulai atau berhenti, dan setiap kali aktivitas dimulai atau berhenti di mana pun di aplikasi. UI thread diblokir saat panggilan fsync()
tertunda yang dijadwalkan oleh apply()
, dan sering menjadi sumber ANR.
2 SharedPreferences menampilkan error penguraian sebagai pengecualian runtime.
Preferences DataStore vs Proto DataStore
Meskipun Preference DataStore dan Proto DataStore mengizinkan penyimpanan data, keduanya dilakukan dengan cara yang berbeda:
- Preference DataStore, seperti SharedPreferences, mengakses data berdasarkan kunci, tanpa menentukan skema awal.
- Proto DataStore menentukan skema menggunakan Buffering Protokol. Menggunakan Protobuf memungkinkan penyimpanan data dengan jenis yang dikenali. Protobuf ini lebih cepat, lebih kecil, lebih sederhana, dan tidak terlalu ambigu dibandingkan dengan XML dan format data serupa lainnya. Meskipun Proto DataStore mengharuskan Anda mempelajari mekanisme serialisasi baru, kami meyakini bahwa keunggulan jenis yang dikenali yang diberikan oleh Proto DataStore tersebut bermanfaat.
Room vs DataStore
Jika memerlukan update sebagian, integritas referensial, atau set data yang besar/kompleks, sebaiknya gunakan Room, bukan DataStore. DataStore cocok untuk set data kecil atau sederhana dan tidak mendukung update sebagian atau integritas referensial.
5. Ringkasan Preferences DataStore
Preferences DataStore API mirip dengan SharedPreferences, tetapi memiliki beberapa perbedaan penting:
- Menangani pembaruan data secara transaksional
- Mengekspos Flow yang menampilkan status data saat ini
- Tidak memiliki metode persisten data (
apply()
,commit()
) - Tidak menampilkan referensi yang dapat diubah ke status internalnya
- Mengekspos API yang mirip dengan
Map
danMutableMap
dengan kunci yang diketik
Mari kita lihat cara menambahkannya ke project dan memigrasikan SharedPreferences ke DataStore.
Menambahkan dependensi
Update file build.gradle untuk menambahkan dependensi Preference DataStore berikut:
implementation "androidx.datastore:datastore-preferences:1.0.0"
6. Menyimpan data di Preferences DataStore
Meskipun flag showCompleted
dan sortOrder
adalah preferensi pengguna, saat ini keduanya ditampilkan sebagai dua objek yang berbeda. Jadi, salah satu tujuan kita adalah menyatukan dua flag ini di class UserPreferences
dan menyimpannya di UserPreferencesRepository
menggunakan DataStore. Saat ini, flag showCompleted
disimpan di memori, di TasksViewModel
.
Mari mulai dengan membuat class data UserPreferences
di UserPreferencesRepository
. Seharusnya saat ini hanya ada satu kolom: showCompleted
. Kita akan menambahkan tata urutannya nanti.
data class UserPreferences(val showCompleted: Boolean)
Membuat DataStore
Untuk membuat instance DataStore, kita menggunakan delegasi preferencesDataStore
, dengan Context
sebagai penerima. Dalam codelab ini, mari lakukan hal ini di TasksActivity
agar lebih praktis:
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
Delegasi preferencesDataStore
memastikan bahwa kita memiliki satu instance DataStore dengan nama tersebut di aplikasi kita. Saat ini, UserPreferencesRepository
diimplementasikan sebagai singleton, karena dapat mempertahankan sortOrderFlow
dan menghindari terpengaruh siklus proses TasksActivity
. Karena UserPreferenceRepository
akan kompatibel dengan data dari DataStore serta tidak akan membuat dan mempertahankan objek baru apa pun, kita dapat menghapus implementasi singleton:
- Hapus
companion object
- Setel
constructor
menjadi publik
UserPreferencesRepository
harus mendapatkan instance DataStore
sebagai parameter konstruktor. Untuk saat ini, kita dapat menetapkan Context
sebagai parameter karena dibutuhkan oleh SharedPreferences, tetapi kita akan menghapusnya nanti.
class UserPreferencesRepository(
private val userPreferencesStore: DataStore<UserPreferences>,
context: Context
) { ... }
Mari kita memperbarui konstruksi UserPreferencesRepository
di TasksActivity
dan memasukkan dataStore
:
viewModel = ViewModelProvider(
this,
TasksViewModelFactory(
TasksRepository,
UserPreferencesRepository(dataStore, this)
)
).get(TasksViewModel::class.java)
Membaca data dari Preferences DataStore
Preferences DataStore mengekspos data yang disimpan di Flow<Preferences>
yang akan ditampilkan setiap kali preferensi berubah. Kita tidak ingin mengekspos seluruh objek Preferences
, melainkan objek UserPreferences
. Untuk melakukannya, kita harus memetakan Flow<Preferences>
, mendapatkan nilai Boolean yang diinginkan, berdasarkan kunci dan membuat objek UserPreferences
.
Jadi, hal pertama yang perlu kita lakukan adalah menentukan kunci show_completed
- ini adalah nilai booleanPreferencesKey
yang dapat kita deklarasikan sebagai anggota dalam objek PreferencesKeys
pribadi.
private object PreferencesKeys {
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
}
Mari mengekspos userPreferencesFlow: Flow<UserPreferences>
, yang dikonstruksikan berdasarkan dataStore.data: Flow<Preferences>
, yang kemudian dipetakan, untuk mengambil preferensi yang tepat:
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.map { preferences ->
// Get our show completed value, defaulting to false if not set:
val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED]?: false
UserPreferences(showCompleted)
}
Menangani pengecualian saat membaca data
Saat DataStore membaca data dari file, IOExceptions
akan muncul saat terjadi error selama pembacaan data. Kita dapat menanganinya dengan menggunakan operator Flow catch()
sebelum map()
dan memunculkan emptyPreferences()
jika pengecualian yang ditampilkan adalah IOException
. Jika jenis pengecualian lain ditampilkan, pilih tampilkan kembali.
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
// Get our show completed value, defaulting to false if not set:
val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED]?: false
UserPreferences(showCompleted)
}
Menulis data ke Preferences DataStore
Untuk menulis data, DataStore menawarkan fungsi DataStore.edit(transform: suspend (MutablePreferences) -> Unit)
penangguhan, yang menerima blok transform
yang memungkinkan kita memperbarui status di DataStore secara transaksional.
MutablePreferences
yang diteruskan ke blok transformasi akan diperbarui dengan semua edit yang telah dijalankan sebelumnya. Semua perubahan pada MutablePreferences
dalam blok transform
akan diterapkan ke disk setelah transform
selesai dan sebelum edit
selesai. Menyetel satu nilai di MutablePreferences
tidak akan mengubah semua preferensi lainnya.
Catatan: jangan mencoba mengubah MutablePreferences
di luar blok transformasi.
Mari membuat fungsi penangguhan yang memungkinkan kita memperbarui properti showCompleted
dari UserPreferences
, disebut sebagai updateShowCompleted()
, yang memanggil dataStore.edit()
dan menyetel nilai baru:
suspend fun updateShowCompleted(showCompleted: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.SHOW_COMPLETED] = showCompleted
}
}
edit()
dapat menampilkan IOException
jika terjadi error saat membaca atau menulis ke disk. Jika terjadi error lain dalam blok transformasi, error tersebut akan ditampilkan oleh edit()
.
Pada tahap ini, aplikasi harus dikompilasi tetapi fungsi yang baru saja dibuat di UserPreferencesRepository
tidak akan digunakan.
7. SharedPreferences ke Preferences DataStore
Tata urutan disimpan di SharedPreferences. Mari kita pindahkan ke DataStore. Untuk melakukannya, mulai dengan mengupdate UserPreferences
untuk menyimpan tata urutan:
data class UserPreferences(
val showCompleted: Boolean,
val sortOrder: SortOrder
)
Bermigrasi dari SharedPreferences
Untuk memigrasikannya ke DataStore, kita perlu mengupdate builder dataStore agar dapat meneruskan SharedPreferencesMigration
ke daftar migrasi. DataStore akan otomatis bermigrasi dari SharedPreferences
ke DataStore. Migrasi akan dijalankan sebelum akses data apa pun dapat terjadi di DataStore. Artinya, migrasi Anda harus berhasil sebelum DataStore.data
menampilkan nilai apa pun dan sebelum DataStore.edit()
dapat memperbarui data.
Catatan: kunci hanya dimigrasikan dari SharedPreferences satu kali, jadi Anda harus berhenti menggunakan SharedPreferences lama setelah kode dimigrasikan ke DataStore.
Pertama, mari kita perbarui pembuatan DataStore di TasksActivity
:
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME,
produceMigrations = { context ->
// Since we're migrating from SharedPreferences, add a migration based on the
// SharedPreferences name
listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
}
)
Kemudian tambahkan sort_order
ke PreferencesKeys
kita:
private object PreferencesKeys {
...
// Note: this has the the same name that we used with SharedPreferences.
val SORT_ORDER = stringPreferencesKey("sort_order")
}
Semua kunci akan dimigrasikan ke DataStore kami dan dihapus dari SharedPreferences preferensi pengguna. Dari Preferences
, sekarang kita dapat memperoleh dan mengupdate SortOrder
berdasarkan kunci SORT_ORDER
.
Membaca tata urutan dari DataStore
Mari memperbarui userPreferencesFlow
untuk mengambil tata urutan dalam transformasi map()
:
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
// Get the sort order from preferences and convert it to a [SortOrder] object
val sortOrder =
SortOrder.valueOf(
preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name)
// Get our show completed value, defaulting to false if not set:
val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED] ?: false
UserPreferences(showCompleted, sortOrder)
}
Menyimpan tata urutan ke DataStore
UserPreferencesRepository
saat ini hanya mengekspos cara sinkron untuk menetapkan flag tata urutan dan memiliki masalah serentak. Kita akan mengekspos dua metode untuk memperbarui tata urutan: enableSortByDeadline()
dan enableSortByPriority()
; keduanya bergantung pada nilai tata urutan saat ini. Namun, jika salah satu metode dipanggil sebelum metode lainnya selesai, nilai akhir akan salah.
Karena DataStore menjamin update data terjadi secara transaksional, kita tidak akan mengalami masalah ini lagi. Mari melakukan perubahan berikut:
- Perbarui
enableSortByDeadline()
danenableSortByPriority()
agar menjadi fungsisuspend
yang menggunakandataStore.edit()
. - Pada blok transformasi
edit()
, kita akan mendapatkancurrentOrder
dari parameter Preferences, bukan mengambilnya dari kolom_sortOrderFlow
. - Daripada memanggil
updateSortOrder(newSortOrder)
, kita dapat langsung memperbarui tata urutan dalam preferensi.
Implementasinya akan terlihat seperti berikut ini.
suspend fun enableSortByDeadline(enable: Boolean) {
// edit handles data transactionally, ensuring that if the sort is updated at the same
// time from another thread, we won't have conflicts
dataStore.edit { preferences ->
// Get the current SortOrder as an enum
val currentOrder = SortOrder.valueOf(
preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name
)
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_PRIORITY) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_DEADLINE
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_PRIORITY
} else {
SortOrder.NONE
}
}
preferences[PreferencesKeys.SORT_ORDER] = newSortOrder.name
}
}
suspend fun enableSortByPriority(enable: Boolean) {
// edit handles data transactionally, ensuring that if the sort is updated at the same
// time from another thread, we won't have conflicts
dataStore.edit { preferences ->
// Get the current SortOrder as an enum
val currentOrder = SortOrder.valueOf(
preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name
)
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_DEADLINE) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_PRIORITY
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_DEADLINE
} else {
SortOrder.NONE
}
}
preferences[PreferencesKeys.SORT_ORDER] = newSortOrder.name
}
}
Anda sekarang dapat menghapus parameter konstruktor context
dan semua penggunaan SharedPreferences.
8. Memperbarui TasksViewModel untuk menggunakan UserPreferencesRepository
Setelah UserPreferencesRepository
menyimpan flag show_completed
dan sort_order
di DataStore dan mengekspos Flow<UserPreferences>
, perbarui TasksViewModel
untuk menggunakannya.
Hapus showCompletedFlow
dan sortOrderFlow
, lalu buat nilai yang disebut userPreferencesFlow
yang diinisialisasi dengan userPreferencesRepository.userPreferencesFlow
:
private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow
Pada pembuatan tasksUiModelFlow
, ganti showCompletedFlow
dan sortOrderFlow
dengan userPreferencesFlow
. Ganti parameter yang sesuai.
Saat memanggil filterSortTasks
, teruskan showCompleted
dan sortOrder
dari userPreferences
. Kode akan terlihat seperti berikut:
private val tasksUiModelFlow = combine(
repository.tasks,
userPreferencesFlow
) { tasks: List<Task>, userPreferences: UserPreferences ->
return@combine TasksUiModel(
tasks = filterSortTasks(
tasks,
userPreferences.showCompleted,
userPreferences.sortOrder
),
showCompleted = userPreferences.showCompleted,
sortOrder = userPreferences.sortOrder
)
}
Fungsi showCompletedTasks()
kini harus diperbarui agar dapat memanggil userPreferencesRepository.updateShowCompleted()
. Karena ini adalah fungsi penangguhan, buat coroutine baru dalam viewModelScope
:
fun showCompletedTasks(show: Boolean) {
viewModelScope.launch {
userPreferencesRepository.updateShowCompleted(show)
}
}
Fungsi userPreferencesRepository
, yaitu enableSortByDeadline()
dan enableSortByPriority()
kini merupakan fungsi penangguhan sehingga juga harus dipanggil dalam coroutine baru yang diluncurkan di viewModelScope
:
fun enableSortByDeadline(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByDeadline(enable)
}
}
fun enableSortByPriority(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByPriority(enable)
}
}
Membersihkan UserPreferencesRepository
Mari kita hapus kolom dan metode yang tidak diperlukan lagi. Anda dapat menghapus hal-hal berikut:
_sortOrderFlow
sortOrderFlow
updateSortOrder()
private val sortOrder: SortOrder
Aplikasi sekarang telah berhasil dikompilasi. Mari kita jalankan untuk melihat apakah flag show_completed
dan sort_order
disimpan dengan benar.
Lihat cabang preferences_datastore
di repo codelab untuk membandingkan perubahan.
9. Rangkuman
Setelah bermigrasi ke Preferences DataStore, mari kita rangkum semua hal yang telah dipelajari:
- SharedPreferences memiliki serangkaian kelemahan - mulai dari API sinkron yang bisa terlihat aman untuk dipanggil di UI thread, tidak ada mekanisme peringatan error, kurangnya API transaksional, dan lainnya.
- DataStore adalah pengganti SharedPreferences yang mengatasi sebagian besar kekurangan API.
- DataStore memiliki API asinkron sepenuhnya yang menggunakan coroutine Kotlin dan Flow, menangani migrasi data, menjamin konsistensi data, dan menangani kerusakan data.