1. Pengantar
Apa itu DataStore?
DataStore adalah solusi penyimpanan data yang baru dan lebih baik yang ditujukan untuk menggantikan SharedPreferences. Dibuat di Flow dan coroutine Kotlin, DataStore menyediakan dua implementasi yang berbeda: Proto DataStore, yang memungkinkan menyimpan objek yang diketik (didukung oleh buffering protokol) dan Preference 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 Preference DataStore dan Proto DataStore, serta keuntungan dari keduanya.
- Cara menggunakan Proto DataStore.
- Cara bermigrasi dari SharedPreferences ke Proto 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 Tampilkan tugas yang telah selesai disimpan di memori. Tata urutan disimpan ke disk menggunakan objek SharedPreferences
.
Karena DataStore memiliki dua implementasi yang berbeda: Preference DataStore dan Proto DataStore, Anda akan mempelajari cara menggunakan Proto DataStore untuk menyelesaikan tugas berikut dalam setiap penerapan:
- Mempertahankan filter status selesai di DataStore.
- Memigrasikan tata urutan dari SharedPreferences ke DataStore.
Sebaiknya gunakan juga codelab Preference DataStore sehingga Anda memahami perbedaan antara keduanya dengan lebih baik.
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 proto_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:
- Menampilkan atau menyembunyikan visibilitas Tugas yang telah selesai - 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.
Meskipun flag showCompleted
dan sortOrder
adalah preferensi pengguna, saat ini keduanya ditampilkan sebagai dua objek yang berbeda. Oleh karena itu, salah satu tujuan kita adalah menyatukan dua flag ini dalam satu class UserPreferences
.
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. Proto DataStore - ringkasan
Salah satu kelemahan dari SharedPreferences dan Preference DataStore adalah tidak ada cara untuk menentukan skema atau memastikan bahwa kunci diakses menggunakan jenis yang benar. Proto DataStore mengatasi masalah ini menggunakan Buffering protokol untuk menentukan skema. Dengan menggunakan proto, DataStore tahu jenis yang disimpan dan akan menyediakannya, sehingga tidak perlu menggunakan kunci.
Mari kita lihat cara menambahkan Proto DataStore dan Protobuf ke project, pengertian dari Buffering protokol, dan cara menggunakannya dengan Proto DataStore serta cara memigrasikan SharedPreferences ke DataStore.
Menambahkan dependensi
Untuk menggunakan Proto DataStore dan mendapatkan Protobuf untuk membuat kode skema, beberapa perubahan pada file build.gradle harus dibuat:
- Menambahkan plugin Protobuf
- Menambahkan dependensi Protobuf dan Proto DataStore
- Mengonfigurasi Protobuf
plugins {
...
id "com.google.protobuf" version "0.8.17"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
6. Menentukan dan menggunakan objek protobuf
Buffering protokol adalah mekanisme untuk melakukan serialisasi data terstruktur. Anda menentukan cara pembuatan struktur data satu kali, lalu compiler membuat kode sumber untuk menulis dan membaca data terstruktur dengan mudah.
Membuat file proto
Anda menentukan skema dalam file proto. Dalam codelab, kami memiliki 2 preferensi pengguna: show_completed
dan sort_order
; saat ini keduanya diwakili sebagai dua objek yang berbeda. Jadi, salah satu sasaran kita adalah menggabungkan dua flag ini dalam class UserPreferences
yang disimpan di DataStore. Daripada menentukan class ini di Kotlin, class ini akan didefinisikan dalam skema Protobuf.
Lihat Panduan bahasa proto untuk mengetahui info sintaksis yang lebih lengkap. Dalam codelab ini, kita hanya akan berfokus pada jenis yang dibutuhkan.
Buat file baru bernama user_prefs.proto
di direktori app/src/main/proto
. Jika Anda tidak melihat struktur folder ini, beralih ke Tampilan project. Dalam Protobuf, setiap struktur ditentukan menggunakan kata kunci message
dan setiap anggota struktur ditentukan di dalam pesan, berdasarkan jenis dan nama, dan akan diberi urutan berdasarkan angka 1. Mari kita tentukan pesan UserPreferences
yang, untuk saat ini, hanya memiliki nilai (logika) boolean yang disebut show_completed
.
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
}
Membuat penserialisasi
Untuk memberi tahu DataStore cara membaca dan menulis jenis data yang telah ditentukan dalam file proto, kita perlu menerapkan Penserialisasi. Penserialisasi juga menentukan nilai default yang akan ditampilkan jika tidak ada data pada disk. Buat file baru bernama UserPreferencesSerializer
dalam paket data
:
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserPreferences {
try {
return UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
7. Menyimpan data di Proto DataStore
Membuat DataStore
Flag showCompleted
disimpan dalam memori di TasksViewModel
, tetapi flag harus disimpan dalam UserPreferencesRepository
di instance DataStore.
Untuk membuat instance DataStore, kita menggunakan delegasi dataStore
, dengan Context
sebagai penerima. Delegasi ini memiliki dua parameter wajib:
- Nama file yang akan ditindaklanjuti oleh DataStore.
- Penserialisasi untuk jenis yang digunakan dengan DataStore. Dalam kasus kami:
UserPreferencesSerializer
.
Dalam codelab ini, mari lakukan hal ini di TasksActivity
agar lebih praktis:
private const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
private const val SORT_ORDER_KEY = "sort_order"
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer
)
Delegasi dataStore
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 Proto DataStore
Proto DataStore mengekspos data yang tersimpan di Flow<UserPreferences>
. Mari kita buat nilai userPreferencesFlow: Flow<UserPreferences>
publik yang ditetapkan ke dataStore.data
:
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
Menangani pengecualian saat membaca data
Saat DataStore membaca data dari file, IOException
akan muncul saat terjadi error selama pembacaan data. Kita dapat mengatasinya dengan menggunakan transformasi Flow catch
dan hanya mencatat error:
private val TAG: String = "UserPreferencesRepo"
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) {
Log.e(TAG, "Error reading sort order preferences.", exception)
emit(UserPreferences.getDefaultInstance())
} else {
throw exception
}
}
Menulis data ke Proto DataStore
Untuk menulis data, DataStore menawarkan fungsi DataStore.updateData()
yang ditangguhkan dengan parameter status saat ini dari UserPreferences
. Untuk mengupdatenya, kita harus mengubah objek preferensi menjadi builder, menetapkan nilai baru, lalu membuat preferensi baru.
updateData()
mengupdate data secara transaksional dalam operasi baca-tulis-modifikasi yang sederhana. Coroutine akan selesai setelah data disimpan di disk.
Mari membuat fungsi penangguhan yang memungkinkan kita memperbarui properti showCompleted
dari UserPreferences
, disebut sebagai updateShowCompleted()
, yang memanggil dataStore.updateData()
dan menyetel nilai baru:
suspend fun updateShowCompleted(completed: Boolean) {
dataStore.updateData { preferences ->
preferences.toBuilder().setShowCompleted(completed).build()
}
}
Pada tahap ini, aplikasi harus dikompilasi tetapi fungsi yang baru saja dibuat di UserPreferencesRepository
tidak akan digunakan.
8. SharedPreferences ke Proto DataStore
Menentukan data yang akan disimpan di proto
Tata urutan disimpan di SharedPreferences. Mari kita pindahkan ke DataStore. Untuk melakukannya, mulai dengan mengupdate UserPreferences
di file proto untuk menyimpan tata urutan. Karena SortOrder
adalah enum
, kita harus menentukannya di UserPreference
. enums
ditentukan dalam protobuf yang mirip dengan Kotlin.
Untuk enumerasi, nilai default adalah nilai pertama yang tercantum dalam definisi jenis enum. Namun, saat bermigrasi dari SharedPreferences, kita perlu mengetahui apakah nilai yang telah didapatkan adalah nilai default atau nilai yang sebelumnya ditetapkan di SharedPreferences. Untuk memudahkannya, kami menetapkan nilai baru ke enum SortOrder
: UNSPECIFIED
dan mencantumkannya terlebih dahulu sehingga nilai baru ini dapat menjadi nilai default.
File user_prefs.proto
akan terlihat seperti ini:
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
// defines tasks sorting order: no order, by deadline, by priority, by deadline and priority
enum SortOrder {
UNSPECIFIED = 0;
NONE = 1;
BY_DEADLINE = 2;
BY_PRIORITY = 3;
BY_DEADLINE_AND_PRIORITY = 4;
}
// user selected tasks sorting order
SortOrder sort_order = 2;
}
Bersihkan dan lakukan build ulang pada project Anda untuk memastikan bahwa objek UserPreferences
baru telah dibuat dan berisi kolom baru.
Setelah SortOrder
ditetapkan dalam file proto, kita dapat menghapus deklarasi dari UserPreferencesRepository
. Hapus:
enum class SortOrder {
NONE,
BY_DEADLINE,
BY_PRIORITY,
BY_DEADLINE_AND_PRIORITY
}
Pastikan impor SortOrder
yang tepat digunakan di mana pun:
import com.codelab.android.datastore.UserPreferences.SortOrder
Di TasksViewModel.filterSortTasks()
, kami melakukan berbagai tindakan berdasarkan jenis SortOrder
. Setelah menambahkan opsi UNSPECIFIED
, kita harus menambahkan kasus lain untuk pernyataan when(sortOrder)
. Karena kami tidak ingin menangani opsi lain selain yang ada saat ini, kami dapat memberikan UnsupportedOperationException
dalam kasus lain.
Fungsi filterSortTasks()
terlihat seperti ini:
private fun filterSortTasks(
tasks: List<Task>,
showCompleted: Boolean,
sortOrder: SortOrder
): List<Task> {
// filter the tasks
val filteredTasks = if (showCompleted) {
tasks
} else {
tasks.filter { !it.completed }
}
// sort the tasks
return when (sortOrder) {
SortOrder.UNSPECIFIED -> filteredTasks
SortOrder.NONE -> filteredTasks
SortOrder.BY_DEADLINE -> filteredTasks.sortedByDescending { it.deadline }
SortOrder.BY_PRIORITY -> filteredTasks.sortedBy { it.priority }
SortOrder.BY_DEADLINE_AND_PRIORITY -> filteredTasks.sortedWith(
compareByDescending<Task> { it.deadline }.thenBy { it.priority }
)
// We shouldn't get any other values
else -> throw UnsupportedOperationException("$sortOrder not supported")
}
}
Bermigrasi dari SharedPreferences
Untuk membantu migrasi, DataStore menentukan class SharedPreferencesMigration
. Metode by dataStore
yang membuat DataStore (digunakan di TasksActivity
), juga mengekspos parameter produceMigrations
. Dalam blok ini, kita membuat daftar DataMigration
yang harus dijalankan untuk instance DataStore ini. Dalam hal ini, kita hanya memiliki satu migrasi: SharedPreferencesMigration
.
Saat mengimplementasikan SharedPreferencesMigration
, blok migrate
menyediakan dua parameter:
SharedPreferencesView
yang memungkinkan data untuk diambil dari SharedPreferences- Data
UserPreferences
saat ini
Objek UserPreferences
harus ditampilkan.
Saat menerapkan blok migrate
, lakukan langkah-langkah berikut:
- Periksa nilai
sortOrder
diUserPreferences
. - Jika ini adalah
SortOrder.UNSPECIFIED
, artinya nilai harus diambil dari SharedPreferences. JikaSortOrder
tidak ada,SortOrder.NONE
dapat digunakan sebagai default. - Setelah mendapatkan urutan penyortiran, kita harus mengonversi objek
UserPreferences
menjadi builder, menetapkan tata urutan, lalu membuat objek lagi dengan memanggilbuild()
. Tidak ada kolom lain yang akan terpengaruh dengan perubahan ini. - Jika nilai
sortOrder
diUserPreferences
bukanlahSortOrder.UNSPECIFIED
, data saat ini yang dapat ditampilkan hanyalah data yang telah didapatkan dimigrate
karena migrasi pasti sudah berjalan dengan baik.
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer,
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(
context,
USER_PREFERENCES_NAME
) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
// Define the mapping from SharedPreferences to UserPreferences
if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
currentData.toBuilder().setSortOrder(
SortOrder.valueOf(
sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!!
)
).build()
} else {
currentData
}
}
)
}
)
Setelah menentukan logika migrasi, selanjutnya DataStore harus diberi tahu bahwa data tersebut harus digunakan. Untuk itu, update builder DataStore dan tetapkan daftar baru ke parameter migrations
yang berisi instance SharedPreferencesMigration
:
private val dataStore: DataStore<UserPreferences> = context.createDataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer,
migrations = listOf(sharedPrefsMigration)
)
Menyimpan urutan penyortiran ke DataStore
Untuk mengupdate urutan penyortiran jika enableSortByDeadline()
dan enableSortByPriority()
dipanggil, lakukan hal berikut:
- Panggil fungsi masing-masing di lambda
dataStore.updateData()
. - Karena
updateData()
adalah fungsi penangguhan,enableSortByDeadline()
danenableSortByPriority()
juga harus dibuat sebagai fungsi penangguhan. - Gunakan
UserPreferences
saat ini yang diterima dariupdateData()
untuk membuat urutan penyortiran baru - Update
UserPreferences
dengan mengonversinya menjadi builder, menetapkan urutan penyortiran baru, lalu melakukan update lagi pada preferensi.
Penerapan enableSortByDeadline()
akan terlihat seperti ini. Anda dapat melakukan perubahan untuk enableSortByPriority()
sendiri.
suspend fun enableSortByDeadline(enable: Boolean) {
// updateData handles data transactionally, ensuring that if the sort is updated at the same
// time from another thread, we won't have conflicts
dataStore.updateData { preferences ->
val currentOrder = preferences.sortOrder
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.toBuilder().setSortOrder(newSortOrder).build()
}
}
Anda sekarang dapat menghapus parameter konstruktor context
dan semua penggunaan SharedPreferences.
9. 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
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
private val sharedPreferences
Aplikasi sekarang telah berhasil dikompilasi. Mari kita jalankan untuk melihat apakah flag show_completed
dan sort_order
disimpan dengan benar.
Lihat cabang proto_datastore
di repo codelab untuk membandingkan perubahan.
10. Rangkuman
Setelah bermigrasi ke Proto 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 pembuatan sinyal 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.