Skip to content

Commit 13674a9

Browse files
authored
Updated Extensions package (#2)
Moved the `DataStore<Preference>` extensions to a `preference` package and added generic `DataStore` extensions
1 parent 2b31145 commit 13674a9

File tree

4 files changed

+199
-44
lines changed

4 files changed

+199
-44
lines changed

demo/src/main/kotlin/com/devbrackets/android/datastoredemo/data/Preferences.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import com.devbrackets.android.datastore.converter.core.EnumValueConverter
88
import com.devbrackets.android.datastore.converter.core.StringBytesValueConverter
99
import com.devbrackets.android.datastore.converter.crypto.EncryptedValueConverter
1010
import com.devbrackets.android.datastore.converter.then
11-
import com.devbrackets.android.datastore.delegate.value
12-
import com.devbrackets.android.datastore.flow
11+
import com.devbrackets.android.datastore.preference.delegate.value
12+
import com.devbrackets.android.datastore.preference.flow
1313
import com.devbrackets.android.datastoredemo.data.model.Month
1414
import com.devbrackets.android.datastoredemo.data.token.DemoAccountTokenValueConverter
1515

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.devbrackets.android.datastore
2+
3+
import androidx.annotation.WorkerThread
4+
import androidx.datastore.core.DataStore
5+
import com.devbrackets.android.datastore.converter.ValueConverter
6+
import com.devbrackets.android.datastore.converter.core.NoOpValueConverter
7+
import kotlinx.coroutines.flow.first
8+
import kotlinx.coroutines.runBlocking
9+
import kotlin.properties.ReadWriteProperty
10+
import kotlin.reflect.KProperty
11+
12+
13+
/**
14+
* A Kotlin delegate to read and write values in a [DataStore].
15+
* For example
16+
* ```kotlin
17+
* var theme by dataStore.value("uiTheme", Theme.FOLLOW_SYSTEM)
18+
* ```
19+
*
20+
* **NOTE:**
21+
* Reading a writing [DataStore] values can be slower than expected and it is recommended
22+
* to perform reads/writes (get/set) on threads other than Main/UI.
23+
*
24+
* @param valueGetter The function that handles retrieving the value from the [DataStore] of [DS]
25+
* @param valueSetter The function that handles setting the value in the [DataStore] of [DS]
26+
*/
27+
@WorkerThread
28+
fun <T, DS> DataStore<DS>.value(
29+
valueSetter: suspend (store: DS, value: T) -> DS,
30+
valueGetter: suspend (store: DS) -> T
31+
): ReadWriteProperty<Any, T> {
32+
return value(
33+
valueSetter = valueSetter,
34+
valueGetter = valueGetter,
35+
converter = NoOpValueConverter()
36+
)
37+
}
38+
39+
/**
40+
* A Kotlin delegate to read and write values in a [DataStore].
41+
* For example
42+
* ```kotlin
43+
* var theme by dataStore.value("uiTheme", Theme.FOLLOW_SYSTEM)
44+
* ```
45+
*
46+
* **NOTE:**
47+
* Reading a writing [DataStore] values can be slower than expected and it is recommended
48+
* to perform reads/writes (get/set) on threads other than Main/UI.
49+
*
50+
* @param valueGetter The function that handles retrieving the value from the [DataStore] of [DS]
51+
* @param valueSetter The function that handles setting the value in the [DataStore] of [DS]
52+
* @param converter The [ValueConverter] to convert between the use type [T] (e.g. an Enum)
53+
* and a format that the can be stored in preferences [S]. Supported preference types
54+
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
55+
*/
56+
@WorkerThread
57+
fun <T, S, DS> DataStore<DS>.value(
58+
valueSetter: suspend (store: DS, value: S) -> DS,
59+
valueGetter: suspend (store: DS) -> S,
60+
converter: ValueConverter<T, S>,
61+
): ReadWriteProperty<Any, T> {
62+
return object : ReadWriteProperty<Any, T> {
63+
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
64+
runBlocking {
65+
this@value.updateData { store ->
66+
valueSetter(store, converter.toConverted(value))
67+
}
68+
}
69+
}
70+
71+
override fun getValue(thisRef: Any, property: KProperty<*>): T {
72+
return runBlocking {
73+
data.first().let {
74+
converter.toOriginal(valueGetter(it))
75+
}
76+
}
77+
}
78+
}
79+
}

library/src/main/kotlin/com/devbrackets/android/datastore/PreferencesExt.kt renamed to library/src/main/kotlin/com/devbrackets/android/datastore/preference/PreferencesExt.kt

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
package com.devbrackets.android.datastore
1+
package com.devbrackets.android.datastore.preference
22

33
import androidx.datastore.core.DataStore
44
import androidx.datastore.preferences.core.Preferences
55
import com.devbrackets.android.datastore.converter.ValueConverter
66
import com.devbrackets.android.datastore.converter.core.NoOpValueConverter
7-
import com.devbrackets.android.datastore.delegate.getPreferencesKey
7+
import com.devbrackets.android.datastore.preference.delegate.getPreferencesKey
88
import kotlinx.coroutines.CoroutineScope
99
import kotlinx.coroutines.flow.Flow
1010
import kotlinx.coroutines.flow.SharedFlow
@@ -33,8 +33,8 @@ inline fun <reified T> DataStore<Preferences>.sharedFlow(
3333
started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
3434
replay: Int = 0
3535
): SharedFlow<T> {
36-
return this.sharedFlow(
37-
key = key,
36+
return sharedFlow(
37+
key = getPreferencesKey(key),
3838
defaultValue = defaultValue,
3939
converter = NoOpValueConverter(),
4040
scope = scope,
@@ -60,13 +60,48 @@ inline fun <reified T> DataStore<Preferences>.sharedFlow(
6060
* @param started The strategy that controls when sharing is started and stopped
6161
* @param replay The number of values replayed to new subscribers (cannot be negative, defaults to zero).
6262
*/
63-
inline fun <reified T, reified S> DataStore<Preferences>.sharedFlow(
63+
inline fun <T, reified S> DataStore<Preferences>.sharedFlow(
6464
key: String,
6565
defaultValue: T,
6666
converter: ValueConverter<T, S>,
6767
scope: CoroutineScope,
6868
started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
6969
replay: Int = 0
70+
): SharedFlow<T> {
71+
return sharedFlow(
72+
key = getPreferencesKey(key),
73+
defaultValue = defaultValue,
74+
converter = converter,
75+
scope = scope,
76+
started = started,
77+
replay = replay
78+
)
79+
}
80+
81+
/**
82+
* A Kotlin delegate to retrieve a [SharedFlow] for values in a [Preferences] [DataStore].
83+
* For example
84+
* ```kotlin
85+
* val themeFlow = dataStore.sharedFlow("uiTheme", Theme.FOLLOW_SYSTEM, EnumValueConverter(Theme::class), viewModelScope)
86+
* ```
87+
*
88+
* @param key The unique id used to store and retrieve the preference, typically this is a
89+
* descriptive id such as "uiTheme"
90+
* @param defaultValue The value to return when [key] isn't contained by the [DataStore]
91+
* @param converter The [ValueConverter] to convert between the use type (e.g. an Enum)
92+
* and a format that the can be stored in preferences. Supported preference types
93+
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
94+
* @param scope The [CoroutineScope] to use when constructing the [SharedFlow]
95+
* @param started The strategy that controls when sharing is started and stopped
96+
* @param replay The number of values replayed to new subscribers (cannot be negative, defaults to zero).
97+
*/
98+
fun <T, S> DataStore<Preferences>.sharedFlow(
99+
key: Preferences.Key<S>,
100+
defaultValue: T,
101+
converter: ValueConverter<T, S>,
102+
scope: CoroutineScope,
103+
started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
104+
replay: Int = 0
70105
): SharedFlow<T> {
71106
return this.flow(
72107
key = key,
@@ -115,16 +150,39 @@ inline fun <reified T> DataStore<Preferences>.flow(
115150
* and a format that the can be stored in preferences. Supported preference types
116151
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
117152
*/
118-
inline fun <reified T, reified S> DataStore<Preferences>.flow(
153+
inline fun <T, reified S> DataStore<Preferences>.flow(
119154
key: String,
120155
defaultValue: T,
121156
converter: ValueConverter<T, S>
122157
): Flow<T> {
123-
val prefKey: Preferences.Key<S> = getPreferencesKey(key)
158+
return flow(
159+
key = getPreferencesKey(key),
160+
defaultValue = defaultValue,
161+
converter = converter
162+
)
163+
}
124164

165+
/**
166+
* A Kotlin delegate to retrieve a [Flow] for values in a [Preferences] [DataStore].
167+
* For example
168+
* ```kotlin
169+
* val themeFlow = dataStore.flow("uiTheme", Theme.FOLLOW_SYSTEM, EnumValueConverter(Theme::class))
170+
* ```
171+
*
172+
* @param key The [Preferences.Key] used to store and retrieve the preference
173+
* @param defaultValue The value to return when [key] isn't contained by the [DataStore]
174+
* @param converter The [ValueConverter] to convert between the use type (e.g. an Enum)
175+
* and a format that the can be stored in preferences. Supported preference types
176+
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
177+
*/
178+
fun <T, S> DataStore<Preferences>.flow(
179+
key: Preferences.Key<S>,
180+
defaultValue: T,
181+
converter: ValueConverter<T, S>
182+
): Flow<T> {
125183
return data.map { prefs ->
126184
prefs.getOrDefault(
127-
key = prefKey,
185+
key = key,
128186
defaultValue = defaultValue,
129187
converter = converter
130188
)
@@ -139,7 +197,7 @@ inline fun <reified T, reified S> DataStore<Preferences>.flow(
139197
* @param key The [Preferences.Key] used to retrieve the [Preferences] value
140198
* @param defaultValue The value to return when [key] isn't defined in [Preferences]
141199
*/
142-
inline fun <reified T> Preferences.getOrDefault(
200+
fun <T> Preferences.getOrDefault(
143201
key: Preferences.Key<T>,
144202
defaultValue: T
145203
): T {
@@ -160,7 +218,8 @@ inline fun <reified T> Preferences.getOrDefault(
160218
* @param converter The [ValueConverter] used to convert between the stored type [S]
161219
* and the expected read/write type [T]
162220
*/
163-
inline fun <reified T, S> Preferences.getOrDefault(
221+
@Suppress("UNCHECKED_CAST")
222+
fun <T, S> Preferences.getOrDefault(
164223
key: Preferences.Key<S>,
165224
defaultValue: T,
166225
converter: ValueConverter<T, S>

library/src/main/kotlin/com/devbrackets/android/datastore/delegate/DataStoreDelegate.kt renamed to library/src/main/kotlin/com/devbrackets/android/datastore/preference/delegate/PreferenceDelegate.kt

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
package com.devbrackets.android.datastore.delegate
1+
package com.devbrackets.android.datastore.preference.delegate
22

33
import androidx.annotation.WorkerThread
44
import androidx.datastore.core.DataStore
55
import androidx.datastore.preferences.core.*
66
import com.devbrackets.android.datastore.converter.ValueConverter
77
import com.devbrackets.android.datastore.converter.core.NoOpValueConverter
8-
import com.devbrackets.android.datastore.getOrDefault
9-
import kotlinx.coroutines.flow.first
10-
import kotlinx.coroutines.runBlocking
8+
import com.devbrackets.android.datastore.preference.getOrDefault
9+
import com.devbrackets.android.datastore.value
1110
import kotlin.properties.ReadWriteProperty
12-
import kotlin.reflect.KProperty
1311

1412
/**
1513
* A Kotlin delegate to read and write values in a [Preferences] [DataStore].
@@ -27,12 +25,12 @@ import kotlin.reflect.KProperty
2725
* @param defaultValue The value to return when [key] isn't contained by the [DataStore]
2826
*/
2927
@WorkerThread
30-
inline fun <reified T : Any?> DataStore<Preferences>.value(
28+
inline fun <reified T> DataStore<Preferences>.value(
3129
key: String,
3230
defaultValue: T
3331
): ReadWriteProperty<Any, T> {
3432
return value(
35-
key = key,
33+
key = getPreferencesKey(key),
3634
defaultValue = defaultValue,
3735
converter = NoOpValueConverter()
3836
)
@@ -57,38 +55,57 @@ inline fun <reified T : Any?> DataStore<Preferences>.value(
5755
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
5856
*/
5957
@WorkerThread
60-
inline fun <reified T, reified S> DataStore<Preferences>.value(
58+
inline fun <T, reified S> DataStore<Preferences>.value(
6159
key: String,
6260
defaultValue: T,
6361
converter: ValueConverter<T, S>
6462
): ReadWriteProperty<Any, T> {
65-
return object : ReadWriteProperty<Any, T> {
66-
private val prefKey: Preferences.Key<S> = getPreferencesKey(key)
67-
68-
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
69-
runBlocking {
70-
set(value)
71-
}
72-
}
63+
return value(
64+
key = getPreferencesKey(key),
65+
defaultValue = defaultValue,
66+
converter = converter
67+
)
68+
}
7369

74-
override fun getValue(thisRef: Any, property: KProperty<*>): T {
75-
return runBlocking {
76-
data.first().getOrDefault(
77-
key = prefKey,
78-
defaultValue = defaultValue,
79-
converter = converter
80-
)
81-
}
82-
}
70+
/**
71+
* A Kotlin delegate to read and write values in a [Preferences] [DataStore] with value transformations.
72+
* For example
73+
* ```kotlin
74+
* var theme by dataStore.value("uiTheme", Theme.FOLLOW_SYSTEM, EnumValueConverter(Theme::class))
75+
* ```
76+
*
77+
* **NOTE:**
78+
* Reading a writing [DataStore] values can potentially be slower than expected and it is recommended
79+
* to perform reads/writes (get/set) on threads other than Main/UI.
80+
*
81+
* @param key The [Preferences.Key] used to store and retrieve the preference
82+
* @param defaultValue The value to return when [key] isn't contained by the [DataStore]
83+
* @param converter The [ValueConverter] to convert between the use type (e.g. an Enum)
84+
* and a format that the can be stored in preferences. Supported preference types
85+
* are `Int`, `Double`, `String`, `Boolean`, `Float`, and `Long`.
86+
*/
87+
@WorkerThread
88+
fun <T, S> DataStore<Preferences>.value(
89+
key: Preferences.Key<S>,
90+
defaultValue: T,
91+
converter: ValueConverter<T, S>
92+
): ReadWriteProperty<Any, T> {
93+
val defaultStoreValue = converter.toConverted(defaultValue)
8394

84-
private suspend fun set(value: T): T {
85-
this@value.edit { prefs ->
86-
prefs[prefKey] = converter.toConverted(value)
95+
return value(
96+
valueSetter = { store, value ->
97+
store.toMutablePreferences().apply {
98+
this[key] = value
8799
}
88-
89-
return value
90-
}
91-
}
100+
},
101+
valueGetter = { store ->
102+
store.getOrDefault(
103+
key = key,
104+
defaultValue = defaultStoreValue
105+
)
106+
},
107+
converter = converter
108+
)
92109
}
93110

94111
/**

0 commit comments

Comments
 (0)