Preferences DataStore

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, dan Flow, serta mengetahui cara menggunakan ViewModelProvider.Factory untuk membuat instance ViewModel.
  • 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.

  1. Mulai Android Studio.
  2. Di jendela Welcome to Android Studio, klik Get from VCS.

61c42d01719e5b6d.png

  1. Dalam dialog Get from Version Control, pastikan Git dipilih untuk Version control.

9284cfbe17219bbb.png

  1. Tempelkan URL kode yang tersedia ke kotak URL.
  2. Jika ingin, Anda dapat mengubah Directory menjadi sesuatu yang berbeda dengan default yang disarankan.

5ddca7dd0d914255.png

  1. Klik Clone. Android Studio mulai mengambil kode Anda.
  2. Tunggu Android Studio terbuka.
  3. Pilih modul yang tepat untuk kode awal, aplikasi, atau solusi codelab Anda.

2919fe3e0c79d762.png

  1. Klik tombol Run 8de56cba7583251f.png 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.

  1. Download kode awal, buka di Android Studio, lalu jalankan aplikasi. Huruf ditampilkan dalam tata letak linear.
  2. Ketuk opsi menu di pojok kanan atas. Tata letak beralih ke tata letak Petak.
  3. Keluar dan luncurkan kembali aplikasi. Anda dapat melakukannya menggunakan opsi Stop 'app' f782441b99bdd0a4.pngdan Run 'app' d203bd07cbce5954.pngdi 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.

  1. 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

  1. Tambahkan paket bernama data dan buat class Kotlin bernama SettingsDataStore di dalamnya.
  2. Tambahkan parameter konstruktor ke class SettingsDataStore dari jenis Context.
class SettingsDataStore(context: Context) {}
  1. Di luar class SettingsDataStore, deklarasikan private const val yang disebut LAYOUT_PREFERENCES_NAME, lalu tetapkan nilai string layout_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"
  1. Masih di luar class, buat instance DataStore menggunakan delegasi preferencesDataStore. Karena menggunakan Preferences Datastore, Anda harus meneruskan Preferences sebagai jenis datastore. Selain itu, tetapkan name datastore ke LAYOUT_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:

  1. Untuk mengimplementasikan class SettingsDataStore, langkah pertama adalah membuat kunci yang menyimpan nilai Boolean yang menentukan apakah setelan pengguna merupakan tata letak linear. Buat properti class private yang disebut IS_LINEAR_LAYOUT_MANAGER, lalu lakukan inisialisasi menggunakan booleanPreferencesKey() yang meneruskan nama kunci is_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().

  1. Buat fungsi suspend bernama saveLayoutToPreferencesStore() yang menggunakan dua parameter: setelan tata letak Boolean, dan Context.
suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager: Boolean, context: Context) {

}
  1. 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.

  1. Tampilkan preferenceFlow: Flow<UserPreferences>, yang dikonstruksikan berdasarkan dataStore.data: Flow<Preferences>, petakan untuk mengambil preferensi Boolean. Karena Datastore kosong saat pertama kali dijalankan, tampilkan true 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
   }
  1. 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.

  1. SharedPreference DataStore memunculkan IOException ketika terjadi error saat membaca data. Di deklarasi preferenceFlow, sebelum map(), gunakan operator catch() untuk menangkap IOException dan memunculkan emptyPreferences(). 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:

  1. Deklarasikan variabel class private yang disebut SettingsDataStore dari jenis SettingsDataStore. Buat variabel ini lateinit karena Anda akan melakukan inisialisasi nanti.
private lateinit var SettingsDataStore: SettingsDataStore
  1. Di akhir fungsi onViewCreated(), lakukan inisialisasi variabel baru lalu teruskan requireContext() ke konstruktor SettingsDataStore.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   ...
   // Initialize SettingsDataStore
   SettingsDataStore = SettingsDataStore(requireContext())
}

Membaca dan mengamati data

  1. Di LetterListFragment, di dalam metode onViewCreated(), di bawah inisialisasi SettingsDataStore, konversikan preferenceFlow ke Livedata menggunakan asLiveData(). Lampirkan observer dan teruskan viewLifecycleOwner sebagai pemilik.
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { })
  1. Di dalam observer, tetapkan setelan tata letak baru ke variabel isLinearLayoutManager. Lakukan panggilan ke fungsi chooseLayout() 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.

  1. Di LetterListFragment, di dalam fungsi onOptionsItemSelected(), di akhir kasus R.id.action_switch_layout, luncurkan coroutine dengan menggunakan lifecycleScope. Di dalam blok launch, lakukan panggilan ke saveLayoutToPreferencesStore() yang meneruskan isLinearLayoutManager dan context.
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
       }
  1. Jalankan aplikasi. Klik opsi menu untuk mengubah tata letak aplikasi.

  1. 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' f782441b99bdd0a4.pngdan Run 'app' d203bd07cbce5954.pngdi Android Studio).

cd2c31f27dfb5157.png

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.

  1. Di LetterListFragment, di dalam onViewCreated(), di akhir observer preferenceFlow, di bawah panggilan ke chooseLayout(). Gambar ulang menu dengan memanggil invalidateOptionsMenu() pada activity.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   ...
   SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
           ...
           // Redraw the menu
           activity?.invalidateOptionsMenu()
   })
}
  1. Jalankan kembali aplikasi lalu ubah tata letak.
  2. Keluar dan luncurkan kembali aplikasi. Perhatikan bahwa ikon menu sekarang diperbarui dengan benar.

1c8cf63c8d175aad.png

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 nilai int, gunakanintPreferencesKey().
  • Preferences DataStore menyediakan fungsi edit() yang memperbarui data secara transaksional dalam DataStore.

10. Pelajari lebih lanjut

Blog

Memilih Penyimpanan Data dengan Jetpack DataStore