1. Sebelum memulai
Pengantar
Di unit ini, Anda telah mempelajari cara menggunakan SQL dan Room untuk menyimpan data secara lokal di perangkat. SQL dan Room adalah alat yang canggih. Namun, jika Anda tidak memerlukan penyimpanan data relasional, DataStore dapat memberikan solusi sederhana. Komponen DataStore Jetpack adalah cara yang bagus untuk menyimpan set data kecil dan sederhana dengan overhead rendah. DataStore memiliki dua implementasi yang berbeda, yaitu Preferences DataStore
dan Proto DataStore
.
Preferences DataStore
menyimpan key-value pair. Nilainya dapat berupa jenis data dasar Kotlin, sepertiString
,Boolean
, danInteger
. Database ini tidak menyimpan set data yang kompleks. Tidak memerlukan skema yang telah ditetapkan sebelumnya. Kasus penggunaan utamaPreferences Datastore
adalah untuk menyimpan preferensi pengguna di perangkatnya.Proto DataStore
menyimpan jenis data kustom. Implementasi ini memerlukan skema yang telah ditentukan sebelumnya dan memetakan definisi proto dengan struktur objek.
Hanya Preferences DataStore
yang dibahas dalam codelab ini, tetapi Anda dapat membaca Proto DataStore
lebih lanjut di dokumentasi DataStore.
Preferences DataStore
adalah cara yang tepat untuk menyimpan setelan yang dikontrol pengguna. Dalam codelab ini, Anda akan mempelajari cara menerapkan DataStore
untuk melakukannya.
Prasyarat:
- Menyelesaikan kursus Dasar-Dasar Android dengan Compose melalui codelab Membaca dan Memperbarui Data dengan Room.
Yang akan Anda butuhkan
- Komputer yang memiliki akses internet dan Android Studio
- Perangkat atau emulator
- Kode awal untuk aplikasi Dessert Release
Yang akan Anda build
Aplikasi Dessert Release menampilkan daftar rilis Android. Ikon di panel aplikasi mengalihkan tata letak antara tampilan petak dan tampilan daftar.
Dalam status saat ini, aplikasi tidak mempertahankan pemilihan tata letak. Saat Anda menutup aplikasi, pilihan tata letak tidak akan disimpan dan setelan akan kembali ke pilihan default. Dalam codelab ini, Anda akan menambahkan DataStore
ke aplikasi Dessert Release dan menggunakannya untuk menyimpan preferensi pemilihan tata letak.
2. Mendownload kode awal
Klik link berikut guna mendownload semua kode untuk codelab ini:
Atau jika mau, Anda dapat meng-clone kode Dessert Release dari GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout starter
- Di Android Studio, buka folder
basic-android-kotlin-compose-training-dessert-release
. - Buka kode aplikasi Dessert Release di Android Studio.
3. Menyiapkan dependensi
Tambahkan kode berikut ke dependencies
di file app/build.gradle.kts
:
implementation("androidx.datastore:datastore-preferences:1.0.0")
4. Mengimplementasikan repositori preferensi pengguna
- Pada paket
data
, buat class baru bernamaUserPreferencesRepository
.
- Di konstruktor
UserPreferencesRepository
, tentukan properti nilai pribadi untuk mewakili instance objekDataStore
dengan jenisPreferences
.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
}
DataStore
menyimpan key-value pair. Untuk mengakses nilai, Anda harus menentukan kunci.
- Buat
companion object
di dalam classUserPreferencesRepository
. - Gunakan fungsi
booleanPreferencesKey()
untuk menentukan kunci dan teruskan namais_linear_layout
. Serupa dengan nama tabel SQL, kunci perlu menggunakan format garis bawah. Kunci ini digunakan untuk mengakses nilai boolean yang menunjukkan apakah tata letak linear harus ditampilkan.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
}
...
}
Menulis ke DataStore
Anda membuat dan mengubah nilai dalam DataStore
dengan meneruskan lambda ke metode edit()
. Lambda meneruskan instance MutablePreferences
, yang dapat Anda gunakan untuk memperbarui nilai di DataStore
. Semua update di dalam lambda ini dijalankan sebagai satu transaksi. Dengan kata lain, update bersifat atomik — semuanya terjadi dalam satu waktu. Jenis update ini mencegah situasi saat beberapa nilai diperbarui, sedangkan yang lainnya tidak.
- Buat fungsi penangguhan, lalu beri nama
saveLayoutPreference()
. - Dalam fungsi
saveLayoutPreference()
, panggil metodeedit()
pada objekdataStore
.
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit {
}
}
- Agar kode lebih mudah dibaca, tentukan nama untuk
MutablePreferences
yang disediakan di isi lambda. Gunakan properti tersebut untuk menyetel nilai dengan kunci yang Anda tentukan dan boolean akan diteruskan ke fungsisaveLayoutPreference()
.
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT] = isLinearLayout
}
}
Membaca dari DataStore
Setelah Anda membuat cara untuk menulis isLinearLayout
ke dalam dataStore
, lakukan langkah-langkah berikut untuk membacanya:
- Buat properti di
UserPreferencesRepository
dari jenisFlow<Boolean>
yang disebutisLinearLayout
.
val isLinearLayout: Flow<Boolean> =
- Anda dapat menggunakan properti
DataStore.data
untuk menampilkan nilaiDataStore
. SetelisLinearLayout
ke propertidata
objekDataStore
.
val isLinearLayout: Flow<Boolean> = dataStore.data
Properti data
adalah Flow
dari objek Preferences
. Objek Preferences
berisi semua key-value pair di DataStore. Setiap kali data di DataStore diperbarui, objek Preferences
baru akan dimunculkan ke dalam Flow
.
- Gunakan fungsi peta untuk mengonversi
Flow<Preferences>
menjadiFlow<Boolean>
.
Fungsi ini menerima lambda dengan objek Preferences
saat ini sebagai parameter. Anda dapat menetapkan kunci yang telah ditentukan sebelumnya untuk mendapatkan preferensi tata letak. Perlu diingat bahwa nilai mungkin tidak ada jika saveLayoutPreference
belum dipanggil, jadi Anda juga harus menyediakan nilai default.
- Tentukan
true
untuk menyetel default ke tampilan tata letak linear.
val isLinearLayout: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
Penanganan pengecualian
Setiap kali Anda berinteraksi dengan sistem file di perangkat, mungkin saja ada yang gagal. Misalnya, file mungkin tidak ada, atau disk penuh atau dilepas. Saat DataStore
membaca dan menulis data dari file, IOExceptions
dapat terjadi saat mengakses DataStore
. Anda menggunakan operator catch{}
untuk menangkap pengecualian dan menangani kegagalan ini.
- Di objek pendamping, terapkan properti string
TAG
yang tidak dapat diubah dan digunakan untuk logging.
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
const val TAG = "UserPreferencesRepo"
}
Preferences DataStore
menampilkanIOException
ketika terjadi error saat membaca data. Di blok inisialisasiisLinearLayout
, sebelummap()
, gunakan operatorcatch{}
untuk menangkapIOException
.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
- Di blok catch, jika ada
IOexception
, catat error dan buatemptyPreferences()
. Jika jenis pengecualian lain ditampilkan, sebaiknya tampilkan kembali pengecualian tersebut. Dengan memunculkanemptyPreferences()
saat terjadi error, fungsi peta masih dapat dipetakan ke nilai default.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {
if(it is IOException) {
Log.e(TAG, "Error reading preferences.", it)
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
5. Melakukan inisialisasi DataStore
Dalam codelab ini, Anda harus menangani injeksi dependensi secara manual. Oleh karena itu, Anda harus menyediakan class UserPreferencesRepository
dengan Preferences DataStore
secara manual. Ikuti langkah-langkah berikut untuk memasukkan DataStore
ke dalam UserPreferencesRepository
.
- Temukan paket
dessertrelease
. - Dalam direktori ini, buat class baru bernama
DessertReleaseApplication
dan implementasikan classApplication
. Ini adalah container untuk DataStore Anda.
class DessertReleaseApplication: Application() {
}
- Di dalam file
DessertReleaseApplication.kt
, tetapi di luar classDessertReleaseApplication
, deklarasikanprivate const val
yang disebutLAYOUT_PREFERENCE_NAME
. - Tetapkan variabel
LAYOUT_PREFERENCE_NAME
nilai stringlayout_preferences
, yang kemudian dapat Anda gunakan sebagai namaPreferences Datastore
yang Anda buat instance-nya di langkah berikutnya.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
- Masih berada di luar isi class
DessertReleaseApplication
, tetapi dalam fileDessertReleaseApplication.kt
, buat properti nilai pribadi jenisDataStore<Preferences>
yang disebutContext.dataStore
menggunakan delegasipreferencesDataStore
. TeruskanLAYOUT_PREFERENCE_NAME
untuk parametername
dari delegasipreferencesDataStore
.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
- Di dalam isi class
DessertReleaseApplication
, buat instancelateinit var
dariUserPreferencesRepository
.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
}
- Ganti metode
onCreate()
.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
}
}
- Di dalam metode
onCreate()
, lakukan inisialisasiuserPreferencesRepository
dengan membuatUserPreferencesRepository
dengandataStore
sebagai parameternya.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
userPreferencesRepository = UserPreferencesRepository(dataStore)
}
}
- Tambahkan baris berikut di dalam tag
<application>
dalam fileAndroidManifest.xml
.
<application
android:name=".DessertReleaseApplication"
...
</application>
Pendekatan ini menentukan class DessertReleaseApplication
sebagai titik entri aplikasi. Tujuan kode ini adalah untuk melakukan inisialisasi dependensi yang ditentukan di class DessertReleaseApplication
sebelum meluncurkan MainActivity
.
6. Menggunakan UserPreferencesRepository
Menyediakan repositori untuk ViewModel
Setelah UserPreferencesRepository
tersedia melalui injeksi dependensi, Anda dapat menggunakannya di DessertReleaseViewModel
.
- Di
DessertReleaseViewModel
, buat propertiUserPreferencesRepository
sebagai parameter konstruktor.
class DessertReleaseViewModel(
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
...
}
- Dalam objek pendamping
ViewModel
, di blokviewModelFactory initializer
, dapatkan instanceDessertReleaseApplication
menggunakan kode berikut.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
...
}
}
}
}
- Buat instance
DessertReleaseViewModel
dan teruskanuserPreferencesRepository
.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
DessertReleaseViewModel(application.userPreferencesRepository)
}
}
}
}
UserPreferencesRepository
kini dapat diakses oleh ViewModel. Langkah berikutnya adalah menggunakan kemampuan baca dan tulis UserPreferencesRepository
yang Anda implementasikan sebelumnya.
Menyimpan preferensi tata letak
- Edit fungsi
selectLayout()
diDessertReleaseViewModel
untuk mengakses repositori preferensi dan memperbarui preferensi tata letak. - Ingatlah bahwa penulisan ke
DataStore
dilakukan secara asinkron dengan fungsisuspend
. Mulai Coroutine baru untuk memanggil fungsisaveLayoutPreference()
repositori preferensi.
fun selectLayout(isLinearLayout: Boolean) {
viewModelScope.launch {
userPreferencesRepository.saveLayoutPreference(isLinearLayout)
}
}
Membaca preferensi tata letak
Di bagian ini, Anda akan memfaktorkan ulang uiState: StateFlow
yang ada di ViewModel
untuk mencerminkan isLinearLayout: Flow
dari repositori.
- Hapus kode yang melakukan inisialisasi properti
uiState
keMutableStateFlow(DessertReleaseUiState)
.
val uiState: StateFlow<DessertReleaseUiState> =
Preferensi tata letak linear dari repositori memiliki dua kemungkinan nilai, yaitu benar atau salah, dalam bentuk Flow<Boolean>
. Nilai ini harus dipetakan ke status UI.
- Setel
StateFlow
ke hasil transformasi koleksimap()
yang dipanggil diisLinearLayout Flow
.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
}
- Tampilkan instance class data
DessertReleaseUiState
, dengan meneruskanisLinearLayout Boolean
. Layar menggunakan status UI ini untuk menentukan string dan ikon yang benar untuk ditampilkan.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
UserPreferencesRepository.isLinearLayout
adalah Flow
yang bersifat cold. Namun, untuk memberikan status ke UI, sebaiknya gunakan hot flow, seperti StateFlow
, sehingga status selalu tersedia langsung ke UI.
- Gunakan fungsi
stateIn()
untuk mengonversiFlow
menjadiStateFlow
. - Fungsi
stateIn()
menerima tiga parameter:scope
,started
, daninitialValue
. TeruskanviewModelScope
,SharingStarted.WhileSubscribed(5_000)
, danDessertReleaseUiState()
untuk parameter ini.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = DessertReleaseUiState()
)
- Luncurkan aplikasi. Perhatikan bahwa Anda dapat mengklik ikon beralih untuk beralih antara tata letak petak dan tata letak linear.
Selamat! Anda berhasil menambahkan Preferences DataStore
ke aplikasi untuk menyimpan preferensi tata letak pengguna.
7. Mendapatkan kode solusi
Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout main
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi, lihat di GitHub.