Skip to content

Commit 16c1a1b

Browse files
Migrating to Preferences DataStore
1 parent f5a0edd commit 16c1a1b

File tree

4 files changed

+132
-63
lines changed

4 files changed

+132
-63
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ dependencies {
6868
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
6969
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
7070
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
71+
implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"
7172

7273
// testing
7374
testImplementation "junit:junit:$junitVersion"

app/src/main/java/com/codelab/android/datastore/data/UserPreferencesRepository.kt

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@
1717
package com.codelab.android.datastore.data
1818

1919
import android.content.Context
20-
import androidx.core.content.edit
21-
import kotlinx.coroutines.flow.MutableStateFlow
22-
import kotlinx.coroutines.flow.StateFlow
20+
import android.util.Log
21+
import androidx.datastore.DataStore
22+
import androidx.datastore.preferences.PreferenceDataStoreFactory
23+
import androidx.datastore.preferences.Preferences
24+
import androidx.datastore.preferences.SharedPreferencesMigration
25+
import kotlinx.coroutines.flow.Flow
26+
import kotlinx.coroutines.flow.catch
27+
import kotlinx.coroutines.flow.map
28+
import java.io.File
29+
import java.io.IOException
2330

2431
private const val USER_PREFERENCES_NAME = "user_preferences"
32+
private const val USER_PREFERENCES_STORE_FILE_NAME = "user.preferences_pb"
2533
private const val SORT_ORDER_KEY = "sort_order"
34+
private const val SHOW_COMPLETED_KEY = "show_completed"
2635

2736
enum class SortOrder {
2837
NONE,
@@ -31,70 +40,120 @@ enum class SortOrder {
3140
BY_DEADLINE_AND_PRIORITY
3241
}
3342

43+
data class UserPreferences(
44+
val showCompleted: Boolean,
45+
val sortOrder: SortOrder
46+
)
47+
48+
/**
49+
* Extension function on Preferences to easily get the sort order
50+
*/
51+
private fun Preferences.getSortOrder(): SortOrder {
52+
val order = getString(SORT_ORDER_KEY, SortOrder.NONE.name)
53+
return SortOrder.valueOf(order)
54+
}
55+
56+
/**
57+
* Extension function on Preferences to easily set the sort order
58+
*/
59+
private fun Preferences.withSortOrder(newSortOrder: SortOrder) =
60+
this.toBuilder().setString(SORT_ORDER_KEY, newSortOrder.name).build()
61+
3462
/**
3563
* Class that handles saving and retrieving user preferences
3664
*/
3765
class UserPreferencesRepository private constructor(context: Context) {
3866

39-
private val sharedPreferences =
40-
context.applicationContext.getSharedPreferences(USER_PREFERENCES_NAME, Context.MODE_PRIVATE)
67+
private val TAG: String = "UserPreferencesRepo"
4168

42-
// Keep the sort order as a stream of changes
43-
private val _sortOrderFlow = MutableStateFlow(sortOrder)
44-
val sortOrderFlow: StateFlow<SortOrder> = _sortOrderFlow
69+
private val dataStore: DataStore<Preferences> by lazy {
70+
PreferenceDataStoreFactory().create(
71+
produceFile = {
72+
File(
73+
context.applicationContext.filesDir,
74+
USER_PREFERENCES_STORE_FILE_NAME
75+
)
76+
},
77+
// Since we're migrating from SharedPreferences, add a migration based on the
78+
// SharedPreferences name
79+
migrationProducers = listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
80+
)
81+
}
4582

4683
/**
47-
* Get the sort order. By default, sort order is None.
84+
* Get the user preferences flow.
4885
*/
49-
private val sortOrder: SortOrder
50-
get() {
51-
val order = sharedPreferences.getString(SORT_ORDER_KEY, SortOrder.NONE.name)
52-
return SortOrder.valueOf(order ?: SortOrder.NONE.name)
86+
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
87+
.catch { exception ->
88+
// dataStore.data throws an IOException when an error is encountered when reading data
89+
if (exception is IOException) {
90+
Log.e(TAG, "Error reading preferences.", exception)
91+
emit(Preferences.empty())
92+
} else {
93+
throw exception
94+
}
95+
}.map { preferences ->
96+
// Get the sort order from preferences and convert it to a [SortOrder] object
97+
val sortOrder = preferences.getSortOrder()
98+
val showCompleted = preferences.getBoolean(SHOW_COMPLETED_KEY, false)
99+
UserPreferences(showCompleted, sortOrder)
53100
}
54101

55-
fun enableSortByDeadline(enable: Boolean) {
56-
val currentOrder = sortOrderFlow.value
57-
val newSortOrder =
58-
if (enable) {
59-
if (currentOrder == SortOrder.BY_PRIORITY) {
60-
SortOrder.BY_DEADLINE_AND_PRIORITY
61-
} else {
62-
SortOrder.BY_DEADLINE
63-
}
64-
} else {
65-
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
66-
SortOrder.BY_PRIORITY
102+
/**
103+
* Enable / disable sort by deadline.
104+
*/
105+
suspend fun enableSortByDeadline(enable: Boolean) {
106+
// updateData handles data transactionally, ensuring that if the sort is updated at the same
107+
// time from another thread, we won't have conflicts
108+
dataStore.updateData { currentPreferences ->
109+
val currentOrder = currentPreferences.getSortOrder()
110+
val newSortOrder =
111+
if (enable) {
112+
if (currentOrder == SortOrder.BY_PRIORITY) {
113+
SortOrder.BY_DEADLINE_AND_PRIORITY
114+
} else {
115+
SortOrder.BY_DEADLINE
116+
}
67117
} else {
68-
SortOrder.NONE
118+
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
119+
SortOrder.BY_PRIORITY
120+
} else {
121+
SortOrder.NONE
122+
}
69123
}
70-
}
71-
updateSortOrder(newSortOrder)
72-
_sortOrderFlow.value = newSortOrder
124+
currentPreferences.withSortOrder(newSortOrder)
125+
}
73126
}
74127

75-
fun enableSortByPriority(enable: Boolean) {
76-
val currentOrder = sortOrderFlow.value
77-
val newSortOrder =
78-
if (enable) {
79-
if (currentOrder == SortOrder.BY_DEADLINE) {
80-
SortOrder.BY_DEADLINE_AND_PRIORITY
81-
} else {
82-
SortOrder.BY_PRIORITY
83-
}
84-
} else {
85-
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
86-
SortOrder.BY_DEADLINE
128+
/**
129+
* Enable / disable sort by priority.
130+
*/
131+
suspend fun enableSortByPriority(enable: Boolean) {
132+
// updateData handles data transactionally, ensuring that if the sort is updated at the same
133+
// time from another thread, we won't have conflicts
134+
dataStore.updateData { currentPreferences ->
135+
val currentOrder = currentPreferences.getSortOrder()
136+
val newSortOrder =
137+
if (enable) {
138+
if (currentOrder == SortOrder.BY_DEADLINE) {
139+
SortOrder.BY_DEADLINE_AND_PRIORITY
140+
} else {
141+
SortOrder.BY_PRIORITY
142+
}
87143
} else {
88-
SortOrder.NONE
144+
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
145+
SortOrder.BY_DEADLINE
146+
} else {
147+
SortOrder.NONE
148+
}
89149
}
90-
}
91-
updateSortOrder(newSortOrder)
92-
_sortOrderFlow.value = newSortOrder
150+
currentPreferences.withSortOrder(newSortOrder)
151+
}
93152
}
94153

95-
private fun updateSortOrder(sortOrder: SortOrder) {
96-
sharedPreferences.edit {
97-
putString(SORT_ORDER_KEY, sortOrder.name)
154+
suspend fun updateShowCompleted(showCompleted: Boolean) {
155+
dataStore.updateData { currentPreferences ->
156+
currentPreferences.toBuilder().setBoolean(SHOW_COMPLETED_KEY, showCompleted).build()
98157
}
99158
}
100159

app/src/main/java/com/codelab/android/datastore/ui/TasksViewModel.kt

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ package com.codelab.android.datastore.ui
1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.ViewModelProvider
2121
import androidx.lifecycle.asLiveData
22+
import androidx.lifecycle.viewModelScope
2223
import com.codelab.android.datastore.data.SortOrder
2324
import com.codelab.android.datastore.data.Task
2425
import com.codelab.android.datastore.data.TasksRepository
26+
import com.codelab.android.datastore.data.UserPreferences
2527
import com.codelab.android.datastore.data.UserPreferencesRepository
26-
import kotlinx.coroutines.flow.MutableStateFlow
2728
import kotlinx.coroutines.flow.combine
29+
import kotlinx.coroutines.launch
2830

2931
data class TasksUiModel(
3032
val tasks: List<Task>,
@@ -38,23 +40,23 @@ class TasksViewModel(
3840
private val userPreferencesRepository: UserPreferencesRepository
3941
) : ViewModel() {
4042

41-
// Keep the show completed filter as a stream of changes
42-
private val showCompletedFlow = MutableStateFlow(false)
43-
44-
// Keep the sort order as a stream of changes
45-
private val sortOrderFlow = userPreferencesRepository.sortOrderFlow
43+
// Keep the user preferences as a stream of changes
44+
private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow
4645

4746
// Every time the sort order, the show completed filter or the list of tasks emit,
4847
// we should recreate the list of tasks
4948
private val tasksUiModelFlow = combine(
5049
repository.tasks,
51-
showCompletedFlow,
52-
sortOrderFlow
53-
) { tasks: List<Task>, showCompleted: Boolean, sortOrder: SortOrder ->
50+
userPreferencesFlow
51+
) { tasks: List<Task>, userPreferences: UserPreferences ->
5452
return@combine TasksUiModel(
55-
tasks = filterSortTasks(tasks, showCompleted, sortOrder),
56-
showCompleted = showCompleted,
57-
sortOrder = sortOrder
53+
tasks = filterSortTasks(
54+
tasks,
55+
userPreferences.showCompleted,
56+
userPreferences.sortOrder
57+
),
58+
showCompleted = userPreferences.showCompleted,
59+
sortOrder = userPreferences.sortOrder
5860
)
5961
}
6062
val tasksUiModel = tasksUiModelFlow.asLiveData()
@@ -82,15 +84,21 @@ class TasksViewModel(
8284
}
8385

8486
fun showCompletedTasks(show: Boolean) {
85-
showCompletedFlow.value = show
87+
viewModelScope.launch {
88+
userPreferencesRepository.updateShowCompleted(show)
89+
}
8690
}
8791

8892
fun enableSortByDeadline(enable: Boolean) {
89-
userPreferencesRepository.enableSortByDeadline(enable)
93+
viewModelScope.launch {
94+
userPreferencesRepository.enableSortByDeadline(enable)
95+
}
9096
}
9197

9298
fun enableSortByPriority(enable: Boolean) {
93-
userPreferencesRepository.enableSortByPriority(enable)
99+
viewModelScope.launch {
100+
userPreferencesRepository.enableSortByPriority(enable)
101+
}
94102
}
95103
}
96104

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ ext {
4343
constraintLayoutVersion = '2.0.2'
4444
coreVersion = '1.3.2'
4545
coroutinesVersion = '1.3.9'
46+
dataStoreVersion = '1.0.0-alpha01'
4647
materialVersion = '1.2.1'
4748
lifecycleVersion = '2.2.0'
4849

0 commit comments

Comments
 (0)