Skip to content

improve native serialization and timestamp fixes #371

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged

Conversation

vpodlesnyak
Copy link
Contributor

  • added GeoPoints
  • added native serialization of DocumentReference
  • fix for Timestamp extensions
  • improve debugging

@@ -453,6 +453,8 @@ external object firebase {
fun update(field: FieldPath, value: Any?, vararg moreFieldsAndValues: Any?): Promise<Unit>
fun delete(): Promise<Unit>
fun onSnapshot(next: (snapshot: DocumentSnapshot) -> Unit, error: (error: Error) -> Unit): ()->Unit

fun isEqual(other: DocumentReference): Boolean
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proper equality checks help a lot when writing tests in the applications using this SDK

@@ -88,6 +88,7 @@ db.collection("cities").document("LA").set(City.serializer(), city, encodeDefaul
```

The `encodeDefaults` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values.
Using [@EncodeDefault](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) on properties is a recommended way to locally override the behavior set with `encodeDefaults`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it helped me a lot so why not to mention it for other devs

actual typealias NativeGeoPoint = com.google.firebase.firestore.GeoPoint

/** A class representing a Firebase GeoPoint. */
@Serializable(with = GeoPointSerializer::class)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this approach is similar to a PR #312

@@ -32,8 +32,8 @@ expect class Timestamp internal constructor(nativeValue: NativeTimestamp): BaseT
}

fun Timestamp.Companion.fromMilliseconds(milliseconds: Double): Timestamp =
Timestamp((milliseconds / 1000).toLong(), (milliseconds * 1000).toInt() % 1000000)
fun Timestamp.toMilliseconds(): Double = seconds * 1000 + (nanoseconds / 1000.0)
Timestamp((milliseconds / 1000).toLong(), ((milliseconds % 1000) * 1000000).toInt())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bug fix here. nanoseconds weren't calculated properly. added a test

fun Query.where(field: String, inArray: List<Any>? = null, arrayContainsAny: List<Any>? = null) = _where(field, inArray, arrayContainsAny)
fun Query.where(path: FieldPath, inArray: List<Any>? = null, arrayContainsAny: List<Any>? = null) = _where(path, inArray, arrayContainsAny)
/** @return a native value of a wrapper or self. */
private val Any.value get() = when (this) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this unwrapping is needed to properly support querying by natively supported classes. added a test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am I right to assume that this change is required to perform a query that requires providing a timestamp field?
e.g. where("endAt", greaterThan = Timestamp.now())

I am unable to do so in 1.8.1 right now and am hoping this fixes it :)

@vpodlesnyak
Copy link
Contributor Author

vpodlesnyak commented Apr 11, 2023

@Reedyuk / @nbransby please take a look when you have time. this PR is a follow up for a PR #312. cheers

@Reedyuk
Copy link
Collaborator

Reedyuk commented Apr 12, 2023

@nbransby

@esafirm
Copy link

esafirm commented Apr 14, 2023

This PR should close #324 right?

Comment on lines 31 to 35
/** @return a map extracted from the encoded data. */
expect fun encodedAsMap(encoded: Any?): Map<String, Any?>
/** @return pairs as raw encoded data. */
expect fun Map<String, Any?>.asEncoded(): Any

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have nativeMapOf and nativeAssertEquals that can be reused instead of adding additional calls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nbransby thanks, I actually missed those. however in order for nativeMapOf and nativeAssertEquals to be available in firebase-firestore tests, the methods shall be moved from the tests folder of the firebase-common to a separate module (lets say firebase-common-test). shall I make this change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point - maybe would be easier to do it on a source set level instead of a separate module?

could add a commonTestUtil source set to firebase-common and include in the test source of firebase-firestore with:

        val commonTest by getting {
            kotlin.srcDir("../firebase-common/src/commonTestUtil/kotlin")
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the suggested approach created a lot of noise - it required setting up not only common but platform tests. extracted a test-utils module instead

@vpodlesnyak vpodlesnyak requested a review from nbransby May 3, 2023 08:27
@nbransby
Copy link
Member

I've noticed a separate issue regarding the TimestampSerializer, compiling a project with the following class defined:

@Serializable
class Data(val timestamp: Timestamp)

fails with

> Task :compileDebugKotlinAndroid FAILED
e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
    class dev.gitlive.firebase.firestore.TimestampSerializer, unresolved supertypes: dev.gitlive.firebase.SpecialValueSerializer

Any ideas on that?

@vpodlesnyak
Copy link
Contributor Author

@nbransby the Timestamp gets encoded/decoded in TimestampTests so it looks suspicious. I'll investigate further by creating a sample app

@nbransby
Copy link
Member

Also is this suppose to work?
image

@vpodlesnyak
Copy link
Contributor Author

@nbransby the serializer not found issue was caused by the fact firebase-common was referenced as implementation instead of api. added a fix

@@ -84,7 +95,7 @@ object DoubleAsTimestampSerializer : SpecialValueSerializer<Double>(
toNativeValue = { value ->
when(value) {
serverTimestamp -> FieldValue.serverTimestamp.nativeValue
else -> Timestamp.fromMilliseconds(value)
else -> Timestamp.fromMilliseconds(value).nativeValue
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bug fix - the native value shall be extracted as in the line above

@nbransby
Copy link
Member

e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:

can you use composition instead of inheritance for the logic shared in SpecialValueSerializer? As making the common module an api import will expose its public api to library consumers but it was designed to hold library internal shared code

@vpodlesnyak
Copy link
Contributor Author

@nbransby

can you use composition instead of inheritance for the logic shared in SpecialValueSerializer? As making the common module an api import will expose its public api to library consumers but it was designed to hold library internal shared code

I might be wrong but I think the error is not related to composition/inheritance, but to whether the end user adds a firebase-common dependency. So we'd either have to make it api or instruct the user to add a firebase-common dependency.

Please also take a look at @FirebaseClassDiscriminator located in firebase-common, it looks like it is meant to be exposed

@nbransby
Copy link
Member

composition won't expose SpecialValueSerializer in the public API of DoubleAsTimestampSerializer therefore the firebase-common dependency is not required to be api (or manually included by client/end user). The readme doesn't actually mention the firebase-common module and adding FirebaseClassDiscriminator there was not ideal but since its there now its probably best to keep the dependency as api.

Instead, for the firebase-common API not intended to be public we should create something like:
https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-internal-serialization-api/

Copy link
Member

@nbransby nbransby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your hard work @vpodlesnyak

@nbransby nbransby merged commit a3c295f into GitLiveApp:master Jun 10, 2023
@nbransby
Copy link
Member

I've noticed gradle build is failing on master now with:
e: file:///Users/nbransby/IdeaProjects/firebase-kotlin-sdk/firebase-app/src/commonTest/kotlin/dev/gitlive/firebase/firebase.kt:7:12 Expected property 'context' has no actual declaration in module <firebase-app_debugUnitTest> for JVM
Do you think that was caused by your changes with test-utils @vpodlesnyak ?
We should really starting gradle build in the CI instead of just gradle assemble!

@vpodlesnyak
Copy link
Contributor Author

vpodlesnyak commented Jun 10, 2023

@nbransby

Thanks for your hard work @vpodlesnyak

my pleasure

build is failing

I see that the failure is caused by unitTests and there are no unit test folders in the repo. I think the error was caused by a commit 30cc1f94 where this bit was removed from the root build.gradle.kts

    tasks.whenTaskAdded {
        enabled = when(name) {
            "compileDebugUnitTestKotlinAndroid" -> false
            "compileReleaseUnitTestKotlinAndroid" -> false
            "testDebugUnitTest" -> false
            "testReleaseUnitTest" -> false
            else -> enabled
        }
    }

@nbransby
Copy link
Member

good spot!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants