Skip to content

Commit c85cae2

Browse files
authored
add serverTimestamp() to FieldValue (#127)
* add serverTimestamp() to FieldValue support Timestamp to Double decoding * fix FirebaseFirestoreTest
1 parent dab6d45 commit c85cae2

File tree

24 files changed

+145
-55
lines changed

24 files changed

+145
-55
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ You can also omit the serializer but this is discouraged due to a [current limit
8686

8787
<h4><a href="https://firebase.google.com/docs/firestore/manage-data/add-data#server_timestamp">Server Timestamp</a></h3>
8888

89-
[Firestore](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue#timestamp) and the [Realtime Database](https://firebase.google.com/docs/reference/android/com/google/firebase/database/ServerValue#TIMESTAMP) provide a sentinel value you can use to set a field in your document to a server timestamp. So you can use these values in custom classes they are of type `Double`:
89+
[Firestore](https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/FieldValue?hl=en#serverTimestamp()) and the [Realtime Database](https://firebase.google.com/docs/reference/android/com/google/firebase/database/ServerValue#TIMESTAMP) provide a sentinel value you can use to set a field in your document to a server timestamp. So you can use these values in custom classes they are of type `Double`:
9090

9191
```kotlin
9292
@Serializable

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ buildscript {
1919
}
2020
}
2121
dependencies {
22-
classpath("com.android.tools.build:gradle:4.1.1")
22+
classpath("com.android.tools.build:gradle:4.0.2")
2323
classpath("de.undercouch:gradle-download-task:4.1.1")
2424
classpath("com.adarshr:gradle-test-logger-plugin:2.0.0")
2525
}

firebase-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
2525
"dependencies": {
26-
"@gitlive/firebase-common": "1.0.0",
26+
"@gitlive/firebase-common": "1.1.0",
2727
"firebase": "8.2.0",
2828
"kotlin": "1.4.21",
2929
"kotlinx-coroutines-core": "1.4.2"

firebase-app/src/jsTest/kotlin/dev/gitlive/firebase/firebase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ import kotlinx.coroutines.promise
99

1010
actual val context: Any = Unit
1111

12-
actual fun runTest(test: suspend () -> Unit) = GlobalScope.promise { test() }.unsafeCast<Unit>()
12+
actual fun runTest(test: suspend () -> Unit) = GlobalScope.promise { test() }.asDynamic()

firebase-auth/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
2525
"dependencies": {
26-
"@gitlive/firebase-app": "1.0.0",
26+
"@gitlive/firebase-app": "1.1.0",
2727
"firebase": "8.2.0",
2828
"kotlin": "1.4.21",
2929
"kotlinx-coroutines-core": "1.4.2"

firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ actual open class FirebaseAuthWebException(code: String?, cause: Throwable): Fir
119119

120120
internal inline fun <T, R> T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.auth.rethrow { function() }
121121

122-
internal inline fun <R> rethrow(function: () -> R): R {
122+
private inline fun <R> rethrow(function: () -> R): R {
123123
try {
124124
return function()
125125
} catch (e: Exception) {

firebase-auth/src/jsTest/kotlin/dev/gitlive/firebase/auth/auth.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ import kotlinx.coroutines.promise
99

1010
actual val context: Any = Unit
1111

12-
actual fun runTest(test: suspend () -> Unit) = GlobalScope.promise { test() }.unsafeCast<Unit>()
12+
actual fun runTest(test: suspend () -> Unit) = GlobalScope.promise { test() }.asDynamic()

firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ package dev.gitlive.firebase
66

77
import kotlinx.serialization.encoding.CompositeDecoder
88
import kotlinx.serialization.KSerializer
9+
import kotlinx.serialization.SerializationException
910
import kotlinx.serialization.descriptors.SerialDescriptor
1011
import kotlinx.serialization.descriptors.StructureKind
1112

12-
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind as StructureKind) {
13+
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) {
1314
StructureKind.CLASS, StructureKind.OBJECT -> (value as Map<*, *>).let { map ->
14-
FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] }
15+
FirebaseClassDecoder(decodeDouble, map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] }
1516
}
1617
StructureKind.LIST -> (value as List<*>).let {
17-
FirebaseCompositeDecoder(it.size) { _, index -> it[index] }
18+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index] }
1819
}
1920
StructureKind.MAP -> (value as Map<*, *>).entries.toList().let {
20-
FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
21+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
2122
}
2223
}

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,28 @@ import kotlinx.serialization.modules.SerializersModule
1616
import kotlinx.serialization.serializer
1717

1818
@Suppress("UNCHECKED_CAST")
19-
inline fun <reified T> decode(value: Any?): T {
19+
inline fun <reified T> decode(value: Any?, noinline decodeDouble: (value: Any?) -> Double? = { null }): T {
2020
val strategy = serializer<T>()
21-
return decode(strategy as DeserializationStrategy<T>, value)
21+
return decode(strategy as DeserializationStrategy<T>, value, decodeDouble)
2222
}
2323

24-
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T {
24+
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, decodeDouble: (value: Any?) -> Double? = { null }): T {
2525
require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" }
26-
return FirebaseDecoder(value).decodeSerializableValue(strategy)
26+
return FirebaseDecoder(value, decodeDouble).decodeSerializableValue(strategy)
2727
}
2828

29-
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder
29+
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder
3030

31-
class FirebaseDecoder(internal val value: Any?) : Decoder {
31+
class FirebaseDecoder(internal val value: Any?, private val decodeDouble: (value: Any?) -> Double?) : Decoder {
3232

3333
override val serializersModule: SerializersModule
3434
get() = EmptySerializersModule
3535

36-
override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor)
36+
override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor, decodeDouble)
3737

3838
override fun decodeString() = decodeString(value)
3939

40-
override fun decodeDouble() = decodeDouble(value)
40+
override fun decodeDouble() = decodeDouble(value, decodeDouble)
4141

4242
override fun decodeLong() = decodeLong(value)
4343

@@ -61,10 +61,11 @@ class FirebaseDecoder(internal val value: Any?) : Decoder {
6161
}
6262

6363
class FirebaseClassDecoder(
64+
decodeDouble: (value: Any?) -> Double?,
6465
size: Int,
6566
private val containsKey: (name: String) -> Boolean,
6667
get: (descriptor: SerialDescriptor, index: Int) -> Any?
67-
) : FirebaseCompositeDecoder(size, get) {
68+
) : FirebaseCompositeDecoder(decodeDouble, size, get) {
6869
private var index: Int = 0
6970

7071
override fun decodeSequentially() = false
@@ -84,6 +85,7 @@ class FirebaseClassDecoder(
8485
}
8586

8687
open class FirebaseCompositeDecoder constructor(
88+
private val decodeDouble: (value: Any?) -> Double?,
8789
private val size: Int,
8890
private val get: (descriptor: SerialDescriptor, index: Int) -> Any?
8991
): CompositeDecoder {
@@ -98,7 +100,7 @@ open class FirebaseCompositeDecoder constructor(
98100

99101
override fun <T> decodeSerializableElement(descriptor: SerialDescriptor,
100102
index: Int, deserializer: DeserializationStrategy<T>, previousValue: T? ): T {
101-
return deserializer.deserialize(FirebaseDecoder(get(descriptor, index)))
103+
return deserializer.deserialize(FirebaseDecoder(get(descriptor, index), decodeDouble))
102104
}
103105

104106
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = decodeBoolean(get(descriptor, index))
@@ -107,7 +109,7 @@ open class FirebaseCompositeDecoder constructor(
107109

108110
override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = decodeChar(get(descriptor, index))
109111

110-
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble(get(descriptor, index))
112+
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble(get(descriptor, index), decodeDouble)
111113

112114
override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = decodeFloat(get(descriptor, index))
113115

@@ -133,10 +135,10 @@ open class FirebaseCompositeDecoder constructor(
133135

134136
private fun decodeString(value: Any?) = value.toString()
135137

136-
private fun decodeDouble(value: Any?) = when(value) {
138+
private fun decodeDouble(value: Any?, decodeDouble: (value: Any?) -> Double?) = when(value) {
137139
is Number -> value.toDouble()
138140
is String -> value.toDouble()
139-
else -> throw SerializationException("Expected $value to be double")
141+
else -> decodeDouble(value) ?: throw SerializationException("Expected $value to be double")
140142
}
141143

142144
private fun decodeLong(value: Any?) = when(value) {

firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ package dev.gitlive.firebase
66

77
import kotlinx.serialization.encoding.CompositeDecoder
88
import kotlinx.serialization.KSerializer
9+
import kotlinx.serialization.SerializationException
910
import kotlinx.serialization.descriptors.SerialDescriptor
1011
import kotlinx.serialization.descriptors.StructureKind
1112

12-
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind as StructureKind) {
13+
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) {
1314
StructureKind.CLASS, StructureKind.OBJECT -> (value as Map<*, *>).let { map ->
14-
FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] }
15+
FirebaseClassDecoder(decodeDouble, map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] }
1516
}
1617
StructureKind.LIST -> (value as List<*>).let {
17-
FirebaseCompositeDecoder(it.size) { _, index -> it[index] }
18+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index] }
1819
}
1920
StructureKind.MAP -> (value as Map<*, *>).entries.toList().let {
20-
FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
21+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
2122
}
2223
}

firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ package dev.gitlive.firebase
66

77
import kotlinx.serialization.encoding.CompositeDecoder
88
import kotlinx.serialization.KSerializer
9+
import kotlinx.serialization.SerializationException
910
import kotlinx.serialization.descriptors.SerialDescriptor
1011
import kotlinx.serialization.descriptors.StructureKind
1112
import kotlin.js.Json
1213

1314
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
14-
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind as StructureKind) {
15+
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) {
1516
StructureKind.CLASS, StructureKind.OBJECT -> (value as Json).let { json ->
16-
FirebaseClassDecoder(js("Object").keys(value).length as Int, { json[it] != undefined }) {
17+
FirebaseClassDecoder(decodeDouble, js("Object").keys(value).length as Int, { json[it] != undefined }) {
1718
desc, index -> json[desc.getElementName(index)]
1819
}
1920
}
2021
StructureKind.LIST -> (value as Array<*>).let {
21-
FirebaseCompositeDecoder(it.size) { _, index -> it[index] }
22+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index] }
2223
}
2324
StructureKind.MAP -> (js("Object").entries(value) as Array<Array<Any>>).let {
24-
FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) get(0) else get(1) } }
25+
FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) get(0) else get(1) } }
2526
}
26-
}
27+
}

firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,12 @@ external object firebase {
339339

340340
open class FieldPath constructor(vararg fieldNames: String)
341341

342+
open class Timestamp {
343+
val seconds: Double
344+
val nanoseconds: Double
345+
fun toMillis(): Double
346+
}
347+
342348
open class Query {
343349
fun get(options: Any? = definedExternally): Promise<QuerySnapshot>
344350
fun where(field: Any, opStr: String, value: Any?): Query
@@ -404,6 +410,7 @@ external object firebase {
404410

405411
abstract class FieldValue {
406412
companion object {
413+
fun serverTimestamp(): FieldValue
407414
fun delete(): FieldValue
408415
fun arrayRemove(vararg elements: Any): FieldValue
409416
fun arrayUnion(vararg elements: Any): FieldValue

firebase-database/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
2525
"dependencies": {
26-
"@gitlive/firebase-app": "1.0.0",
26+
"@gitlive/firebase-app": "1.1.0",
2727
"firebase": "8.2.0",
2828
"kotlin": "1.4.21",
2929
"kotlinx-coroutines-core": "1.4.2"

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ import kotlinx.coroutines.tasks.await
2727
import kotlinx.serialization.DeserializationStrategy
2828
import kotlinx.serialization.SerializationStrategy
2929

30-
inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
30+
@PublishedApi
31+
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
3132
dev.gitlive.firebase.encode(value, shouldEncodeElementDefault, ServerValue.TIMESTAMP)
3233

33-
fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
34+
internal fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
3435
dev.gitlive.firebase.encode(strategy, value, shouldEncodeElementDefault, ServerValue.TIMESTAMP)
3536

3637
@OptIn(FlowPreview::class)

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ import platform.Foundation.*
2727
import kotlin.collections.component1
2828
import kotlin.collections.component2
2929

30-
fun encode(value: Any?, shouldEncodeElementDefault: Boolean) =
30+
@PublishedApi
31+
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
3132
dev.gitlive.firebase.encode(value, shouldEncodeElementDefault, FIRServerValue.timestamp())
32-
fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
33+
34+
internal fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
3335
dev.gitlive.firebase.encode(strategy, value, shouldEncodeElementDefault, FIRServerValue.timestamp())
3436

3537
actual val Firebase.database

firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import kotlinx.coroutines.flow.callbackFlow
1111
import kotlinx.serialization.DeserializationStrategy
1212
import kotlinx.serialization.SerializationStrategy
1313

14-
fun encode(value: Any?, shouldEncodeElementDefault: Boolean) =
14+
@PublishedApi
15+
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
1516
encode(value, shouldEncodeElementDefault, firebase.database.ServerValue.TIMESTAMP)
16-
fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElementDefault: Boolean): Any? =
17+
18+
internal fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElementDefault: Boolean): Any? =
1719
encode(strategy, value, shouldEncodeElementDefault, firebase.database.ServerValue.TIMESTAMP)
1820

1921

firebase-firestore/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
2525
"dependencies": {
26-
"@gitlive/firebase-app": "1.0.0",
26+
"@gitlive/firebase-app": "1.1.0",
2727
"firebase": "8.2.0",
2828
"kotlin": "1.4.21",
2929
"kotlinx-coroutines-core": "1.4.2"

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,32 @@
55
@file:JvmName("android")
66
package dev.gitlive.firebase.firestore
77

8+
import com.google.firebase.Timestamp
9+
import com.google.firebase.firestore.FieldValue
810
import com.google.firebase.firestore.SetOptions
911
import dev.gitlive.firebase.*
12+
import dev.gitlive.firebase.firestore.encode
1013
import kotlinx.coroutines.channels.awaitClose
1114
import kotlinx.coroutines.flow.callbackFlow
1215
import kotlinx.coroutines.runBlocking
1316
import kotlinx.coroutines.tasks.await
1417
import kotlinx.serialization.DeserializationStrategy
1518
import kotlinx.serialization.SerializationStrategy
19+
import kotlinx.serialization.serializer
20+
21+
@PublishedApi
22+
internal inline fun <reified T> decode(value: Any?): T =
23+
decode(value) { (it as? Timestamp)?.run { seconds * 1000 + (nanoseconds / 1000000.0) } }
24+
25+
internal fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T =
26+
decode(strategy, value) { (it as? Timestamp)?.run { seconds * 1000 + (nanoseconds / 1000000.0) } }
27+
28+
@PublishedApi
29+
internal inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean) =
30+
encode(value, shouldEncodeElementDefault, FieldValue.serverTimestamp())
31+
32+
private fun <T> encode(strategy: SerializationStrategy<T> , value: T, shouldEncodeElementDefault: Boolean): Any? =
33+
encode(strategy, value, shouldEncodeElementDefault, FieldValue.serverTimestamp())
1634

1735
actual val Firebase.firestore get() =
1836
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())
@@ -381,6 +399,7 @@ actual typealias FieldPath = com.google.firebase.firestore.FieldPath
381399
actual fun FieldPath(vararg fieldNames: String) = FieldPath.of(*fieldNames)
382400

383401
actual object FieldValue {
402+
actual fun serverTimestamp() = Double.POSITIVE_INFINITY
384403
actual fun delete(): Any = com.google.firebase.firestore.FieldValue.delete()
385404
actual fun arrayUnion(vararg elements: Any): Any = com.google.firebase.firestore.FieldValue.arrayUnion(*elements)
386405
actual fun arrayRemove(vararg elements: Any): Any = com.google.firebase.firestore.FieldValue.arrayRemove(*elements)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ expect class FieldPath
192192
expect fun FieldPath(vararg fieldNames: String): FieldPath
193193

194194
expect object FieldValue {
195+
fun serverTimestamp(): Double
195196
fun delete(): Any
196197
fun arrayUnion(vararg elements: Any): Any
197198
fun arrayRemove(vararg elements: Any): Any
198199
}
199-

0 commit comments

Comments
 (0)