Skip to content

Commit b713011

Browse files
authored
Merge pull request #28 from splendo/feature/custom-update-serializers
fixes for update serializers
2 parents f7a3c68 + bcc0b3c commit b713011

File tree

9 files changed

+179
-120
lines changed

9 files changed

+179
-120
lines changed

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt

+18-1
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,26 @@ fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElement
1313
FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity).apply { encodeSerializableValue(strategy, value) }.value//.also { println("encoded $it") }
1414

1515
inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean, positiveInfinity: Any = Double.POSITIVE_INFINITY): Any? = value?.let {
16-
FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity).apply { encodeSerializableValue(it.firebaseSerializer(), it) }.value
16+
FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity).apply {
17+
if (it is ValueWithSerializer<*> && it.value is T) {
18+
@Suppress("UNCHECKED_CAST")
19+
(it as ValueWithSerializer<T>).let {
20+
encodeSerializableValue(it.serializer, it.value)
21+
}
22+
} else {
23+
encodeSerializableValue(it.firebaseSerializer(), it)
24+
}
25+
}.value
1726
}
1827

28+
/**
29+
* An extension which which serializer to use for value. Handy in updating fields by name or path
30+
* where using annotation is not possible
31+
* @return a value with a custom serializer.
32+
*/
33+
fun <T> T.withSerializer(serializer: SerializationStrategy<T>): Any = ValueWithSerializer(this, serializer)
34+
data class ValueWithSerializer<T>(val value: T, val serializer: SerializationStrategy<T>)
35+
1936
expect fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): CompositeEncoder
2037

2138
class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean, positiveInfinity: Any) : TimestampEncoder(positiveInfinity), Encoder {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.gitlive.firebase.firestore
2+
3+
import com.google.firebase.firestore.FieldValue
4+
5+
actual fun isFieldValue(value: Any) : Boolean = value is FieldValue

firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt

+49-61
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@ internal inline fun <reified T> decode(value: Any?): T =
2222
internal fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T =
2323
decode(strategy, value) { (it as? Timestamp)?.run { seconds * 1000 + (nanoseconds / 1000000.0) } }
2424

25-
@PublishedApi
26-
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
27-
encode(value, shouldEncodeElementDefault, FieldValue.serverTimestamp())
28-
29-
private fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
30-
encode(strategy, value, shouldEncodeElementDefault, FieldValue.serverTimestamp())
31-
3225
actual val Firebase.firestore get() =
3326
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())
3427

@@ -116,27 +109,26 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) {
116109

117110
@JvmName("updateFields")
118111
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<String, Any?>) =
119-
android.takeUnless { fieldsAndValues.isEmpty() }
120-
?.update(
121-
documentRef.android,
122-
fieldsAndValues[0].first,
123-
fieldsAndValues[0].second,
124-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
125-
listOf(field, value?.let { encode(value, true) })
126-
}.toTypedArray()
127-
).let { this }
112+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
113+
?.map { (field, value) -> field to encode(value, true) }
114+
?.let { encoded -> android.update(documentRef.android, encoded.toMap()) }
115+
.let { this }
128116

129117
@JvmName("updateFieldPaths")
130118
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<FieldPath, Any?>) =
131-
android.takeUnless { fieldsAndValues.isEmpty() }
132-
?.update(
133-
documentRef.android,
134-
fieldsAndValues[0].first.android,
135-
fieldsAndValues[0].second,
136-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
137-
listOf(field.android, value?.let { encode(value, true) })
138-
}.toTypedArray()
139-
).let { this }
119+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
120+
?.map { (field, value) -> field.android to encode(value, true) }
121+
?.let { encoded ->
122+
android.update(
123+
documentRef.android,
124+
encoded.first().first,
125+
encoded.first().second,
126+
*encoded.drop(1)
127+
.flatMap { (field, value) -> listOf(field, value) }
128+
.toTypedArray()
129+
)
130+
}
131+
.let { this }
140132

141133
actual fun delete(documentRef: DocumentReference) =
142134
android.delete(documentRef.android).let { this }
@@ -189,27 +181,25 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction)
189181

190182
@JvmName("updateFields")
191183
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<String, Any?>) =
192-
android.takeUnless { fieldsAndValues.isEmpty() }
193-
?.update(
194-
documentRef.android,
195-
fieldsAndValues[0].first,
196-
fieldsAndValues[0].second,
197-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
198-
listOf(field, value?.let { encode(value, true) })
199-
}.toTypedArray()
200-
).let { this }
184+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
185+
?.map { (field, value) -> field to encode(value, true) }
186+
?.let { encoded -> android.update(documentRef.android, encoded.toMap()) }
187+
.let { this }
201188

202189
@JvmName("updateFieldPaths")
203190
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<FieldPath, Any?>) =
204-
android.takeUnless { fieldsAndValues.isEmpty() }
205-
?.update(
206-
documentRef.android,
207-
fieldsAndValues[0].first.android,
208-
fieldsAndValues[0].second,
209-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
210-
listOf(field.android, value?.let { encode(value, true) })
211-
}.toTypedArray()
212-
).let { this }
191+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
192+
?.map { (field, value) -> field.android to encode(value, true) }
193+
?.let { encoded ->
194+
android.update(
195+
documentRef.android,
196+
encoded.first().first,
197+
encoded.first().second,
198+
*encoded.drop(1)
199+
.flatMap { (field, value) -> listOf(field, value) }
200+
.toTypedArray()
201+
)
202+
}.let { this }
213203

214204
actual fun delete(documentRef: DocumentReference) =
215205
android.delete(documentRef.android).let { this }
@@ -264,27 +254,25 @@ actual class DocumentReference(val android: com.google.firebase.firestore.Docume
264254

265255
@JvmName("updateFields")
266256
actual suspend fun update(vararg fieldsAndValues: Pair<String, Any?>) =
267-
android.takeUnless { fieldsAndValues.isEmpty() }
268-
?.update(
269-
fieldsAndValues[0].first,
270-
fieldsAndValues[0].second,
271-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
272-
listOf(field, value?.let { encode(value, true) })
273-
}.toTypedArray()
274-
)
257+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
258+
?.map { (field, value) -> field to encode(value, true) }
259+
?.let { encoded -> android.update(encoded.toMap()) }
275260
?.await()
276261
.run { Unit }
277262

278263
@JvmName("updateFieldPaths")
279264
actual suspend fun update(vararg fieldsAndValues: Pair<FieldPath, Any?>) =
280-
android.takeUnless { fieldsAndValues.isEmpty() }
281-
?.update(
282-
fieldsAndValues[0].first.android,
283-
fieldsAndValues[0].second,
284-
*fieldsAndValues.drop(1).flatMap { (field, value) ->
285-
listOf(field.android, value?.let { encode(value, true) })
286-
}.toTypedArray()
287-
)
265+
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
266+
?.map { (field, value) -> field.android to encode(value, true) }
267+
?.let { encoded ->
268+
android.update(
269+
encoded.first().first,
270+
encoded.first().second,
271+
*encoded.drop(1)
272+
.flatMap { (field, value) -> listOf(field, value) }
273+
.toTypedArray()
274+
)
275+
}
288276
?.await()
289277
.run { Unit }
290278

@@ -449,7 +437,7 @@ actual class DocumentSnapshot(val android: com.google.firebase.firestore.Documen
449437

450438
actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) {
451439
actual val hasPendingWrites: Boolean get() = android.hasPendingWrites()
452-
actual val isFromCache: Boolean get() = android.isFromCache()
440+
actual val isFromCache: Boolean get() = android.isFromCache
453441
}
454442

455443
actual class FieldPath private constructor(val android: com.google.firebase.firestore.FieldPath) {
@@ -458,7 +446,7 @@ actual class FieldPath private constructor(val android: com.google.firebase.fire
458446
}
459447

460448
actual object FieldValue {
461-
actual fun serverTimestamp(): Any = com.google.firebase.firestore.FieldValue.serverTimestamp()
449+
actual fun serverTimestamp(): Any = FieldValue.serverTimestamp()
462450
actual val delete: Any get() = FieldValue.delete()
463451
actual fun arrayUnion(vararg elements: Any): Any = FieldValue.arrayUnion(*elements)
464452
actual fun arrayRemove(vararg elements: Any): Any = FieldValue.arrayRemove(*elements)

firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ abstract class AbstractTimestampSerializer<T> : KSerializer<T> {
2727
abstract override fun deserialize(decoder: Decoder): T
2828

2929
fun encode(encoder: Encoder, value: FirebaseTimestamp?) {
30+
if (value == null) {
31+
encoder.encodeNull()
32+
return
33+
}
3034
val objectEncoder = encoder.beginStructure(descriptor) as FirebaseCompositeEncoder
3135
when (value) {
3236
is FirebaseTimestamp.Value -> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.gitlive.firebase.firestore
2+
3+
import kotlinx.serialization.SerializationStrategy
4+
5+
expect fun isFieldValue(value: Any) : Boolean
6+
7+
@PublishedApi
8+
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
9+
if (value?.let(::isFieldValue) == true) {
10+
value
11+
} else {
12+
dev.gitlive.firebase.encode(value, shouldEncodeElementDefault, FieldValue.serverTimestamp())
13+
}
14+
15+
internal fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElementDefault: Boolean): Any? =
16+
dev.gitlive.firebase.encode(
17+
strategy,
18+
value,
19+
shouldEncodeElementDefault,
20+
FieldValue.serverTimestamp()
21+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.gitlive.firebase.firestore
2+
3+
import cocoapods.FirebaseFirestore.FIRFieldValue
4+
5+
actual fun isFieldValue(value: Any) : Boolean = value is FIRFieldValue

firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt

+28-6
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,16 @@ actual class WriteBatch(val ios: FIRWriteBatch) {
9797
ios.updateData(encode(strategy, data, encodeDefaults) as Map<Any?, *>, documentRef.ios).let { this }
9898

9999
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<String, Any?>) =
100-
ios.updateData(fieldsAndValues.associate { it }, documentRef.ios).let { this }
100+
ios.updateData(
101+
fieldsAndValues.associate { (field, value) -> field to encode(value, true) },
102+
documentRef.ios
103+
).let { this }
101104

102105
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<FieldPath, Any?>) =
103-
ios.updateData(fieldsAndValues.associate { (path, value) -> path.ios to value }, documentRef.ios).let { this }
106+
ios.updateData(
107+
fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) },
108+
documentRef.ios
109+
).let { this }
104110

105111
actual fun delete(documentRef: DocumentReference) =
106112
ios.deleteDocument(documentRef.ios).let { this }
@@ -137,10 +143,16 @@ actual class Transaction(val ios: FIRTransaction) {
137143
ios.updateData(encode(strategy, data, encodeDefaults) as Map<Any?, *>, documentRef.ios).let { this }
138144

139145
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<String, Any?>) =
140-
ios.updateData(fieldsAndValues.associate { it }, documentRef.ios).let { this }
146+
ios.updateData(
147+
fieldsAndValues.associate { (field, value) -> field to encode(value, true) },
148+
documentRef.ios
149+
).let { this }
141150

142151
actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair<FieldPath, Any?>) =
143-
ios.updateData(fieldsAndValues.associate { (path, value) -> path.ios to value }, documentRef.ios).let { this }
152+
ios.updateData(
153+
fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) },
154+
documentRef.ios
155+
).let { this }
144156

145157
actual fun delete(documentRef: DocumentReference) =
146158
ios.deleteDocument(documentRef.ios).let { this }
@@ -186,10 +198,20 @@ actual class DocumentReference(val ios: FIRDocumentReference) {
186198
await { ios.updateData(encode(strategy, data, encodeDefaults) as Map<Any?, *>, it) }
187199

188200
actual suspend fun update(vararg fieldsAndValues: Pair<String, Any?>) =
189-
await { block -> ios.updateData(fieldsAndValues.associate { it }, block) }
201+
await { block ->
202+
ios.updateData(
203+
fieldsAndValues.associate { (field, value) -> field to encode(value, true) },
204+
block
205+
)
206+
}
190207

191208
actual suspend fun update(vararg fieldsAndValues: Pair<FieldPath, Any?>) =
192-
await { block -> ios.updateData(fieldsAndValues.associate { (path, value) -> path.ios to value }, block) }
209+
await { block ->
210+
ios.updateData(
211+
fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) },
212+
block
213+
)
214+
}
193215

194216
actual suspend fun delete() =
195217
await { ios.deleteDocumentWithCompletion(it) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.gitlive.firebase.firestore
2+
3+
import dev.gitlive.firebase.firebase
4+
5+
actual fun isFieldValue(value: Any) : Boolean = value is firebase.firestore.FieldValue

0 commit comments

Comments
 (0)