1. Sebelum memulai
Dalam codelab sebelumnya, Anda telah mempelajari cara menyimpan data di database SQLite menggunakan Room, yaitu lapisan abstraksi database. Codelab ini memperkenalkan Jetpack DataStore. Dibuat di coroutine Kotlin dan Flow, DataStore menyediakan dua implementasi berbeda: Proto DataStore yang menyimpan objek yang diketik, serta Preferences DataStore, yang menyimpan key-value pair.
Dalam codelab praktis ini, Anda akan mempelajari cara menggunakan Preferences DataStore. Proto DataStore berada di luar cakupan codelab ini.
Prasyarat
- Memahami komponen arsitektur Android
ViewModel
,LiveData
, danFlow
, serta mengetahui cara menggunakanViewModelProvider.Factory
untuk membuat instanceViewModel
. - Memahami dasar-dasar konkurensi.
- Mengetahui cara menggunakan coroutine untuk tugas yang berjalan lama.
Yang akan Anda pelajari
- Pengertian DataStore, serta alasan dan waktu penggunaannya.
- Cara menambahkan Preference DataStore ke aplikasi.
Yang Anda perlukan
- Kode awal untuk aplikasi Words (sama dengan kode solusi aplikasi Words dari codelab sebelumnya).
- Komputer yang dilengkapi Android Studio.
Mendownload kode awal untuk codelab ini
Dalam codelab ini, Anda akan memperluas fitur aplikasi Words dari kode solusi sebelumnya. Kode awal mungkin berisi kode yang juga tidak asing bagi Anda dari codelab sebelumnya.
Untuk mendapatkan kode yang dipakai pada codelab ini dari GitHub serta membukanya di Android Studio, lakukan hal berikut.
- Mulai Android Studio.
- Di jendela Welcome to Android Studio, klik Get from VCS.
- Dalam dialog Get from Version Control, pastikan Git dipilih untuk Version control.
- Tempelkan URL kode yang tersedia ke kotak URL.
- Jika ingin, Anda dapat mengubah Directory menjadi sesuatu yang berbeda dengan default yang disarankan.
- Klik Clone. Android Studio mulai mengambil kode Anda.
- Tunggu Android Studio terbuka.
- Pilih modul yang tepat untuk kode awal, aplikasi, atau solusi codelab Anda.
- Klik tombol Run untuk membuat dan menjalankan kode.
2. Ringkasan aplikasi awal
Aplikasi Words terdiri dari dua layar: Layar pertama menampilkan huruf yang dapat dipilih pengguna; layar kedua menampilkan daftar kata yang diawali dengan huruf yang dipilih.
Aplikasi ini memiliki opsi menu bagi pengguna untuk beralih antara tata letak daftar dan petak untuk huruf tersebut.
- Download kode awal, buka di Android Studio, lalu jalankan aplikasi. Huruf ditampilkan dalam tata letak linear.
- Ketuk opsi menu di pojok kanan atas. Tata letak beralih ke tata letak Petak.
- Keluar dan luncurkan kembali aplikasi. Anda dapat melakukannya menggunakan opsi Stop 'app' dan Run 'app' di Android Studio. Perhatikan bahwa saat aplikasi diluncurkan kembali, huruf-huruf akan ditampilkan dalam tata letak linear, bukan dalam Petak.
Perhatikan bahwa pilihan pengguna tidak dipertahankan. Codelab ini menunjukkan cara memperbaiki masalah ini.
Yang akan Anda build
- Di codelab ini, Anda akan mempelajari cara menggunakan Preferences DataStore untuk mempertahankan setelan tata letak di DataStore.
3. Pengantar Preferences DataStore
Preferences DataStore ideal untuk set data kecil yang sederhana, seperti menyimpan detail login, setelan mode gelap, ukuran font, dan sebagainya. DataStore tidak cocok untuk set data yang kompleks, seperti daftar inventaris toko bahan makanan online, atau database siswa. Jika Anda perlu menyimpan set data yang besar atau kompleks, pertimbangkan untuk menggunakan Room, bukan DataStore.
Dengan library Jetpack DataStore, Anda dapat membuat API yang sederhana, aman, dan asinkron untuk menyimpan data. DataStore menyediakan dua implementasi yang berbeda: Preferences DataStore dan Proto DataStore. Meskipun Preferences dan Proto DataStore mengizinkan penyimpanan data, keduanya dilakukan dengan cara yang berbeda:
- Preferences DataStore mengakses dan menyimpan data berdasarkan kunci, tanpa menentukan skema (model database) di awal.
- Proto DataStore menentukan skema menggunakan Buffering Protokol. Dengan menggunakan buffering Protokol, atau Protobuf, memungkinkan Anda mempertahankan data yang banyak diketik. Protobuf ini lebih cepat, lebih kecil, lebih sederhana, dan tidak terlalu ambigu dibandingkan dengan XML dan format data serupa lainnya.
Room versus Datastore: waktu penggunaan
Jika aplikasi Anda perlu menyimpan data besar/kompleks dalam format terstruktur seperti SQL, sebaiknya gunakan Room. Namun, jika Anda hanya perlu menyimpan data sederhana atau dalam jumlah kecil yang dapat disimpan dalam key-value pair, DataStore adalah pilihan ideal.
Proto versus Preferences DataStore: waktu penggunaan
Proto DataStore memiliki jenis yang aman dan efisien, tetapi memerlukan konfigurasi dan penyiapan. Jika data aplikasi Anda cukup sederhana sehingga dapat disimpan di key-value pair, Preferences DataStore adalah pilihan yang lebih baik karena jauh lebih mudah disiapkan.
Menambahkan Preferences DataStore sebagai dependensi
Langkah pertama dalam mengintegrasikan DataStore dengan aplikasi adalah menambahkannya sebagai dependensi.
- Di
build.gradle(Module: Words.app)
, tambahkan dependensi berikut:
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
4. Membuat Preferences DataStore
- Tambahkan paket bernama
data
dan buat class Kotlin bernamaSettingsDataStore
di dalamnya. - Tambahkan parameter konstruktor ke class
SettingsDataStore
dari jenisContext
.
class SettingsDataStore(context: Context) {}
- Di luar class
SettingsDataStore
, deklarasikanprivate const val
yang disebutLAYOUT_PREFERENCES_NAME
, lalu tetapkan nilai stringlayout_preferences
ke dalamnya. Ini adalah nama Preferences Datastore yang akan Anda buat instance-nya di langkah berikutnya.
private const val LAYOUT_PREFERENCES_NAME = "layout_preferences"
- Masih di luar class, buat instance
DataStore
menggunakan delegasipreferencesDataStore
. Karena menggunakan Preferences Datastore, Anda harus meneruskanPreferences
sebagai jenis datastore. Selain itu, tetapkanname
datastore keLAYOUT_PREFERENCES_NAME
.
Kode yang sudah selesai:
private const val LAYOUT_PREFERENCES_NAME = "layout_preferences"
// Create a DataStore instance using the preferencesDataStore delegate, with the Context as
// receiver.
private val Context.dataStore : DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCES_NAME
)
5. Menerapkan Class SettingsDataStore
Seperti yang telah dibahas, Preferences DataStore menyimpan data dalam key-value pair. Pada langkah ini, Anda akan menentukan kunci yang diperlukan untuk menyimpan setelan tata letak, dan menentukan fungsi untuk menulis dan membaca dari Preferences DataStore.
Fungsi jenis kunci
Preferences DataStore tidak menggunakan skema standar seperti Room, tetapi menggunakan fungsi jenis kunci yang sesuai akan menentukan kunci untuk setiap nilai yang Anda simpan di instance DataStore<Preferences>
. Misalnya, untuk menentukan kunci untuk nilai int
, gunakan intPreferencesKey()
, serta untuk nilai string
, gunakan stringPreferencesKey()
. Secara umum, nama fungsi ini diawali dengan jenis data yang ingin Anda simpan di kunci.
Terapkan kode berikut di class data\SettingsDataStore
:
- Untuk mengimplementasikan class
SettingsDataStore
, langkah pertama adalah membuat kunci yang menyimpan nilai Boolean yang menentukan apakah setelan pengguna merupakan tata letak linear. Buat properti classprivate
yang disebutIS_LINEAR_LAYOUT_MANAGER
, lalu lakukan inisialisasi menggunakanbooleanPreferencesKey()
yang meneruskan nama kunciis_linear_layout_manager
sebagai parameter fungsi.
private val IS_LINEAR_LAYOUT_MANAGER = booleanPreferencesKey("is_linear_layout_manager")
Tulis ke Preferences DataStore
Sekarang saatnya menggunakan kunci Anda lalu menyimpan setelan tata letak Boolean di DataStore
. Preferences DataStore menyediakan fungsi penangguhan edit()
yang memperbarui data dalam transaksi di DataStore
secara transaksional. Parameter transformasi fungsi menerima blok kode tempat Anda dapat memperbarui nilai yang diperlukan. Semua kode dalam blok transformasi diperlakukan sebagai transaksi tunggal. Di balik layar, pekerjaan transaksi dipindahkan ke Dispacter.IO
, jadi jangan lupa untuk membuat fungsi suspend
Anda saat memanggil fungsi edit()
.
- Buat fungsi
suspend
bernamasaveLayoutToPreferencesStore()
yang menggunakan dua parameter: setelan tata letak Boolean, danContext
.
suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager: Boolean, context: Context) {
}
- Implementasikan fungsi di atas, panggil
dataStore
.edit()
, dan teruskan blok kode untuk menyimpan nilai baru.
suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager: Boolean, context: Context) {
context.dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT_MANAGER] = isLinearLayoutManager
}
}
Membaca dari Preferences DataStore
Preferences DataStore mengekspos data yang disimpan di Flow<Preferences>
yang muncul setiap kali preferensi berubah. Anda tidak ingin mengekspos seluruh objek Preferences
, hanya nilai Boolean
. Untuk melakukannya, kami memetakan Flow<Preferences>
, dan mendapatkan nilai Boolean
yang diinginkan.
- Tampilkan
preferenceFlow: Flow<UserPreferences>
, yang dikonstruksikan berdasarkandataStore.data: Flow<Preferences>
, petakan untuk mengambil preferensiBoolean
. Karena Datastore kosong saat pertama kali dijalankan, tampilkantrue
secara default.
val preferenceFlow: Flow<Boolean> = context.dataStore.data
.map { preferences ->
// On the first run of the app, we will use LinearLayoutManager by default
preferences[IS_LINEAR_LAYOUT_MANAGER] ?: true
}
- Tambahkan impor berikut jika tidak diimpor secara otomatis:
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
Penanganan pengecualian
Saat DataStore membaca dan menulis data dari file, IOExceptions
dapat terjadi saat mengakses data. Anda menangani hal ini dengan menggunakan operator catch()
untuk menangkap pengecualian.
- SharedPreference DataStore memunculkan
IOException
ketika terjadi error saat membaca data. Di deklarasipreferenceFlow
, sebelummap()
, gunakan operatorcatch()
untuk menangkapIOException
dan memunculkanemptyPreferences()
. Agar tetap sederhana, karena kami tidak mengharapkan jenis pengecualian lain di sini, jika jenis pengecualian lain ditampilkan, tampilkanlah kembali.
val preferenceFlow: Flow<Boolean> = context.dataStore.data
.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
// On the first run of the app, we will use LinearLayoutManager by default
preferences[IS_LINEAR_LAYOUT_MANAGER] ?: true
}
Class data\SettingsDataStore
telah siap digunakan.
6. Menggunakan Class SettingsDataStore
Pada tugas berikutnya ini, Anda akan menggunakan SettingsDataStore
di class LetterListFragment
. Anda akan melampirkan observer ke setelan tata letak serta mengupdate UI-nya.
Terapkan langkah-langkah berikut di LetterListFragment
:
- Deklarasikan variabel class
private
yang disebutSettingsDataStore
dari jenisSettingsDataStore
. Buat variabel inilateinit
karena Anda akan melakukan inisialisasi nanti.
private lateinit var SettingsDataStore: SettingsDataStore
- Di akhir fungsi
onViewCreated()
, lakukan inisialisasi variabel baru lalu teruskanrequireContext()
ke konstruktorSettingsDataStore
.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
// Initialize SettingsDataStore
SettingsDataStore = SettingsDataStore(requireContext())
}
Membaca dan mengamati data
- Di
LetterListFragment
, di dalam metodeonViewCreated()
, di bawah inisialisasiSettingsDataStore
, konversikanpreferenceFlow
keLivedata
menggunakanasLiveData
()
. Lampirkan observer dan teruskanviewLifecycleOwner
sebagai pemilik.
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { })
- Di dalam observer, tetapkan setelan tata letak baru ke variabel
isLinearLayoutManager
. Lakukan panggilan ke fungsichooseLayout()
untuk memperbarui tata letak RecyclerView.
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
isLinearLayoutManager = value
chooseLayout()
})
Fungsi onViewCreated()
yang telah selesai akan terlihat seperti di bawah ini:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
recyclerView = binding.recyclerView
// Initialize SettingsDataStore
SettingsDataStore = SettingsDataStore(requireContext())
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
isLinearLayoutManager = value
chooseLayout()
})
}
Menulis setelan tata letak ke DataStore
Langkah terakhir adalah menulis setelan tata letak ke Preferences DataStore saat pengguna mengetuk opsi menu. Menulis data ke datastore preferensi harus dilakukan secara asinkron di dalam coroutine. Untuk melakukannya di dalam fragmen, gunakan CoroutineScope
yang disebut LifecycleScope
.
LifecycleScope
Komponen berbasis siklus proses seperti fragmen, memberikan dukungan coroutine kelas satu untuk cakupan logis di aplikasi Anda bersama dengan lapisan interoperabilitas dengan LiveData
. LifecycleScope
ditentukan untuk setiap objek Lifecycle
. Setiap coroutine yang diluncurkan dalam cakupan ini akan dibatalkan saat Lifecycle
dihancurkan.
- Di
LetterListFragment
, di dalam fungsionOptionsItemSelected()
, di akhir kasusR.id.
action_switch_layout
, luncurkan coroutine dengan menggunakanlifecycleScope
. Di dalam bloklaunch
, lakukan panggilan kesaveLayoutToPreferencesStore()
yang meneruskanisLinearLayoutManager
dancontext
.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_switch_layout -> {
...
// Launch a coroutine and write the layout setting in the preference Datastore
lifecycleScope.launch {
SettingsDataStore.saveLayoutToPreferencesStore(isLinearLayoutManager, requireContext())
}
...
return true
}
- Jalankan aplikasi. Klik opsi menu untuk mengubah tata letak aplikasi.
- Sekarang uji persistensi Preferences DataStore. Ubah tata letak aplikasi ke tata letak Petak. Keluar dan luncurkan kembali aplikasi (Anda dapat melakukannya menggunakan opsi Stop 'app' dan Run 'app' di Android Studio).
Saat aplikasi diluncurkan kembali, huruf tersebut sekarang ditampilkan dalam tata letak Petak, bukan dalam tata letak Linear. Aplikasi Anda berhasil menyimpan setelan tata letak yang dipilih oleh pengguna.
Perhatikan bahwa meskipun huruf sekarang ditampilkan dalam tata letak Petak, ikon menu tidak diperbarui dengan benar. Selanjutnya, kita akan melihat cara memperbaiki masalah ini.
7. Memperbaiki bug ikon menu
Alasan bug ikon menu adalah, di onViewCreated()
, tata letak RecyclerView diperbarui sesuai dengan setelan tata letak, tetapi bukan ikon menu. Masalah ini dapat diatasi dengan menggambar ulang menu bersama dengan memperbarui tata letak RecyclerView.
Menggambar ulang menu opsi
Setelah menu dibuat, menu tidak akan digambar ulang setiap bingkai karena akan redundan untuk menggambar ulang menu yang sama setiap bingkai. Fungsi invalidateOptionsMenu()
memberi tahu Android untuk menggambar ulang menu opsi.
Anda dapat memanggil fungsi ini saat mengubah sesuatu di menu Opsi, seperti menambahkan item menu, menghapus item, atau mengubah teks atau ikon menu. Dalam hal ini, ikon menu telah diubah. Memanggil metode ini akan mendeklarasikan bahwa menu Opsi telah berubah, sehingga harus dibuat ulang. Metode onCreateOptionsMenu(android.view.Menu)
akan dipanggil ketika nanti perlu ditampilkan.
- Di
LetterListFragment
, di dalamonViewCreated()
, di akhir observerpreferenceFlow
, di bawah panggilan kechooseLayout()
. Gambar ulang menu dengan memanggilinvalidateOptionsMenu()
padaactivity
.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
...
// Redraw the menu
activity?.invalidateOptionsMenu()
})
}
- Jalankan kembali aplikasi lalu ubah tata letak.
- Keluar dan luncurkan kembali aplikasi. Perhatikan bahwa ikon menu sekarang diperbarui dengan benar.
Selamat! Anda telah berhasil menambahkan Preferences DataStore ke aplikasi untuk menyimpan pilihan pengguna.
8. Kode solusi
Kode solusi untuk codelab ini berada dalam project dan modul yang ditampilkan di bawah ini.
9. Ringkasan
- DataStore memiliki API asinkron sepenuhnya yang menggunakan coroutine Kotlin dan Flow, sehingga menjamin konsistensi data.
- Jetpack DataStore adalah solusi penyimpanan data yang memungkinkan Anda menyimpan key-value pair atau objek yang diketik dengan buffering protokol.
- DataStore menyediakan dua implementasi yang berbeda: Preferences DataStore dan Proto DataStore.
- Preferences DataStore tidak menggunakan skema yang telah ditetapkan sebelumnya.
- Preferences DataStore menggunakan fungsi jenis kunci yang sesuai untuk menentukan kunci pada setiap nilai yang perlu Anda simpan di instance
DataStore<Preferences>
. Misalnya, untuk menentukan kunci nilaiint
, gunakanintPreferencesKey()
. - Preferences DataStore menyediakan fungsi
edit()
yang memperbarui data secara transaksional dalamDataStore
.
10. Pelajari lebih lanjut
DataStore
guide
- Referensi DataStore
- Preferences
- android.datastore.preferences.core