Skip to content

Commit 697ed13

Browse files
committed
Update DataStore.kt
1 parent 417eda6 commit 697ed13

File tree

1 file changed

+154
-9
lines changed
  • firebase-common/src/main/java/com/google/firebase/datastore

1 file changed

+154
-9
lines changed

firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,28 @@ import kotlinx.coroutines.flow.firstOrNull
2727
import kotlinx.coroutines.runBlocking
2828

2929
/**
30-
* Don't use this unless you're bridging Java code Use your own [dataStore] directly in Kotlin code,
31-
* or if you're writing new code.
30+
* Wrapper around [DataStore] for easier migration from `SharedPreferences` in Java code.
31+
*
32+
* Automatically migrates data from any `SharedPreferences` that share the same context and name.
33+
*
34+
* There should only ever be _one_ instance of this class per context and name variant.
35+
*
36+
* > Do **NOT** use this _unless_ you're bridging Java code. If you're writing new code, or your >
37+
* code is in Kotlin, then you should create your own singleton that uses [DataStore] directly.
38+
*
39+
* Example:
40+
* ```java
41+
* DataStorage heartBeatStorage = new DataStorage(applicationContext, "FirebaseHeartBeat");
42+
* ```
43+
*
44+
* @property context The [Context] that this data will be saved under.
45+
* @property name What the storage file should be named.
3246
*/
3347
class DataStorage(val context: Context, val name: String) {
34-
private val transforming = ThreadLocal<Boolean>()
48+
/**
49+
* Used to ensure that there's only ever one call to [editSync] per thread; as to avoid deadlocks.
50+
*/
51+
private val editLock = ThreadLocal<Boolean>()
3552

3653
private val Context.dataStore: DataStore<Preferences> by
3754
preferencesDataStore(
@@ -41,26 +58,134 @@ class DataStorage(val context: Context, val name: String) {
4158

4259
private val dataStore = context.dataStore
4360

61+
/**
62+
* Get data from the datastore _synchronously_.
63+
*
64+
* Note that if the key is _not_ in the datastore, while the [defaultValue] will be returned
65+
* instead- it will **not** be saved to the datastore; you'll have to manually do that.
66+
*
67+
* Blocks on the currently running thread.
68+
*
69+
* Example:
70+
* ```java
71+
* Preferences.Key<Long> fireCountKey = PreferencesKeys.longKey("fire-count");
72+
* assert dataStore.get(fireCountKey, 0L) == 0L;
73+
*
74+
* dataStore.putSync(fireCountKey, 102L);
75+
* assert dataStore.get(fireCountKey, 0L) == 102L;
76+
* ```
77+
*
78+
* @param key The typed key of the entry to get data for.
79+
* @param defaultValue A value to default to, if the key isn't found.
80+
*
81+
* @see Preferences.getOrDefault
82+
*/
4483
fun <T> getSync(key: Preferences.Key<T>, defaultValue: T): T = runBlocking {
4584
dataStore.data.firstOrNull()?.get(key) ?: defaultValue
4685
}
4786

87+
/**
88+
* Checks if a key is present in the datastore _synchronously_.
89+
*
90+
* Blocks on the currently running thread.
91+
*
92+
* Example:
93+
* ```java
94+
* Preferences.Key<Long> fireCountKey = PreferencesKeys.longKey("fire-count");
95+
* assert !dataStore.contains(fireCountKey);
96+
*
97+
* dataStore.putSync(fireCountKey, 102L);
98+
* assert dataStore.contains(fireCountKey);
99+
* ```
100+
*
101+
* @param key The typed key of the entry to find.
102+
*/
48103
fun <T> contains(key: Preferences.Key<T>): Boolean = runBlocking {
49104
dataStore.data.firstOrNull()?.contains(key) ?: false
50105
}
51106

107+
/**
108+
* Sets and saves data in the datastore _synchronously_.
109+
*
110+
* Existing values will be overwritten.
111+
*
112+
* Blocks on the currently running thread.
113+
*
114+
* Example:
115+
* ```java
116+
* dataStore.putSync(PreferencesKeys.longKey("fire-count"), 102L);
117+
* ```
118+
*
119+
* @param key The typed key of the entry to save the data under.
120+
* @param value The data to save.
121+
*
122+
* @return The [Preferences] object that the data was saved under.
123+
*/
52124
fun <T> putSync(key: Preferences.Key<T>, value: T): Preferences = runBlocking {
53125
dataStore.edit { it[key] = value }
54126
}
55127

56-
/** Do not modify the returned map (should be obvious, since it's immutable though) */
128+
/**
129+
* Gets all data in the datastore _synchronously_.
130+
*
131+
* Blocks on the currently running thread.
132+
*
133+
* Example:
134+
* ```java
135+
* ArrayList<String> allDates = new ArrayList<>();
136+
*
137+
* for (Map.Entry<Preferences.Key<?>, Object> entry : dataStore.getAllSync().entrySet()) {
138+
* if (entry.getValue() instanceof Set) {
139+
* Set<String> dates = new HashSet<>((Set<String>) entry.getValue());
140+
* if (!dates.isEmpty()) {
141+
* allDates.add(new ArrayList<>(dates));
142+
* }
143+
* }
144+
* }
145+
* ```
146+
*
147+
* @return An _immutable_ map of data currently present in the datastore.
148+
*/
57149
fun getAllSync(): Map<Preferences.Key<*>, Any> = runBlocking {
58150
dataStore.data.firstOrNull()?.asMap() ?: emptyMap()
59151
}
60152

61-
/** Edit calls should not be called within edit calls. Is there a way to prevent this? */
153+
/**
154+
* Transactionally edit data in the datastore _synchronously_.
155+
*
156+
* Edits made within the [transform] callback will be saved (committed) all at once once the
157+
* [transform] block exits.
158+
*
159+
* Because of the blocking nature of this function, you should _never_ call [editSync] within an
160+
* already running [transform] block. Since this can cause a deadlock, [editSync] will instead
161+
* throw an exception if it's caught.
162+
*
163+
* Blocks on the currently running thread.
164+
*
165+
* Example:
166+
* ```java
167+
* dataStore.editSync((pref) -> {
168+
* Long heartBeatCount = pref.get(HEART_BEAT_COUNT_TAG);
169+
* if (heartBeatCount == null || heartBeatCount > 30) {
170+
* heartBeatCount = 0L;
171+
* }
172+
* pref.set(HEART_BEAT_COUNT_TAG, heartBeatCount);
173+
* pref.set(LAST_STORED_DATE, "1970-0-1");
174+
*
175+
* return null;
176+
* });
177+
* ```
178+
*
179+
* @param transform A callback to invoke with the [MutablePreferences] object.
180+
*
181+
* @return The [Preferences] object that the data was saved under.
182+
* @throws IllegalStateException If you attempt to call [editSync] within another [transform]
183+
* block.
184+
*
185+
* @see Preferences.getOrDefault
186+
*/
62187
fun editSync(transform: (MutablePreferences) -> Unit): Preferences = runBlocking {
63-
if (transforming.get() == true) {
188+
if (editLock.get() == true) {
64189
throw IllegalStateException(
65190
"""
66191
Don't call DataStorage.edit() from within an existing edit() callback.
@@ -70,15 +195,35 @@ class DataStorage(val context: Context, val name: String) {
70195
.trimIndent()
71196
)
72197
}
73-
transforming.set(true)
198+
editLock.set(true)
74199
try {
75200
dataStore.edit { transform(it) }
76201
} finally {
77-
transforming.set(false)
202+
editLock.set(false)
78203
}
79204
}
80205
}
81206

82-
/** Helper for Java code */
207+
/**
208+
* Helper method for getting the value out of a [Preferences] object if it exists, else falling back
209+
* to the default value.
210+
*
211+
* This is primarily useful when working with an instance of [MutablePreferences]
212+
* - like when working within an [DataStorage.editSync] callback.
213+
*
214+
* Example:
215+
* ```java
216+
* dataStore.editSync((pref) -> {
217+
* long heartBeatCount = DataStoreKt.getOrDefault(pref, HEART_BEAT_COUNT_TAG, 0L);
218+
* heartBeatCount+=1;
219+
* pref.set(HEART_BEAT_COUNT_TAG, heartBeatCount);
220+
*
221+
* return null;
222+
* });
223+
* ```
224+
*
225+
* @param key The typed key of the entry to get data for.
226+
* @param defaultValue A value to default to, if the key isn't found.
227+
*/
83228
fun <T> Preferences.getOrDefault(key: Preferences.Key<T>, defaultValue: T) =
84229
get(key) ?: defaultValue

0 commit comments

Comments
 (0)