Closed flashadvanced closed 6 years ago
@alickbass any thoughts on this? How are we supposed to decode Timestamp and GeoPoint with FirestoreDecoder? Thanks
@flashadvanced Did you have Geopoint
and Timestamp
extend and conform to the following protocols:
GeoPointType
TimestampType
The README file explicitly mentions this:
Hey @serjooo, sure I conform to them both in my project:
extension GeoPoint: GeoPointType {}
extension Timestamp: TimestampType {}
Have you maybe changed your date decoding strategy? Maybe that's why it tells you Expected to decode Double
. As for the Geopoint
it does inform you that it isn't finding a dictionary to decode. As Geopoint
is an object with the two properties latitude
and longitude
make sure that from Firestore that's what is exactly being sent
I haven't done anything special, I just make the expected attributes from Firestore db to be of Timestamp and GeoPoint types. I also logged the incoming data to make sure what's going on:
- key : "location"
- value : <FIRGeoPoint: (42.125881, 24.791203)>
- key : "dateAdded"
- value : FIRTimestamp: seconds=1536087940 nanoseconds=611000000>
Which means everything is as it should be, right?
Yup the dictionary looks good, should be working... I used the above method for the FIRTimestamp and it worked on my end. Are you able to encode your own objects and persist them to Firestore?
Yeah, values are set to Firestore cloud as expected. For the location attribute I just set
GeoPoint(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
and for the dateAdded attribute I use FieldValue.serverTimestamp()
. This really makes no sense :)
Can you show me the code where you are encoding the MyFirestoreModel
and saving it to Firestore
Sure. As explained I set the location like this:
modelToEncode.location = GeoPoint(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
For the timestamp field I do it a bit differently. I don't keep an attribute in the struct I am encoding. First I encode the model with FirestoreEncoder and then I get the result dictionary and mutate it to add the timestamp field like this (I use a helper function):
func withCreationTimestamp() -> Dictionary {
var modified = self
modified["dateAdded"] = FieldValue.serverTimestamp()
return modified
}
Finally I set the encoded model to the Firestore document reference.
That's exactly your problem, you are saving to Firestore the dateAdded
as a double value which is the FieldValue.serverTimestamp()
, not sure what you are doing with the Geopoint
unless I look at it. However, you shouldn't be writing those dictionaries manually, eventhough you can if you please, there is an easier way of persisting your model to Firestore and that is using the FirestoreEncoder.
Let me provide you with the code:
let encoder = FirestoreEncoder()
let dictionary = encoder.encode(myFirestoreModel)
Now that you used the FirestoreEncoder
to encode your model object into a dictionary to save to Firestore, it will be saved to Firestore with the correct types. So in return when you use the FirestoreDecoder
to decode MyFirestoreModel
it should work without a problem!
I decided to go with FieldValue.serverTimestamp()
as this was the recommended way of saving timestamp to Firestore. Anyways I removed the mutated encoded dictionary part and just set the attribute as default value like this:
init(dateAdded: Timestamp = Timestamp(date: Date(), ...)
which should be valid right? Again it sets the date attribute to Firestore as expected but it cannot be handled properly by the Decoder (prints out the same error).
You will need to delete/modify all the objects that have been saved as a Double for dateAdded so that when you decode with FirestoreDecoder it decodes properly. As for the initializer it looks good. As for the decoder still failing, the only thing I can say is make sure you are persisting the dictionary correctly. If all else fails, I'll try making a project that uses FIRTimestamp and FIRGeopoint with CodableFirebase so that you could try out tomorrow.
Thanks for everything, highly appreciated.
@flashadvanced were you able to figure it out and make it work or should I share a demo project with you?
@serjooo yes please, if you can share the demo project that would be great.
@flashadvanced check this project of mine. It is using FIRTimestamp and I am able to encode and decode easily to get the Timestamp value
@serjooo, thanks a lot for sharing this. It really helped me to locate my silly mistake.
I finally realized what I was doing wrong - I was using FirebaseDecoder
instead of FirestoreDecoder
. Now everything works as expected - both Timestamp and GeoPoint attributes.
@serjooo, thanks a lot for sharing this. It really helped me to locate my silly mistake. I finally realized what I was doing wrong - I was using
FirebaseDecoder
instead ofFirestoreDecoder
. Now everything works as expected - both Timestamp and GeoPoint attributes.
I also had the same problem, I did not realize I was using FirebaseDecode, thank you very much.
@flashadvanced @luistejadaa very glad that the project shared helped you spot what was going wrong! Great job!
Seeing other people making the same mistake I think it is a good idea having FirebaseDecoder renamed to let say RealtimeDecoder. Because Firestore is still a Firebase product and FirebaseDecoder and FirestoreDecoder are both part of the same pod it is really easy to mess things up and then debug for long time not knowing what is going on. What do you guys think? @alickbass
serjoo. I've got problems working with FieldValue.serverTimestamp(). i want to have a timestamp in my model object and use FieldValue.serverTimestamp() as a placeholder. then encode the object and save to firestore. have firestore populate the timestamp field. later fetch the object and decode back to a timestamp on the clients. is that essentially what you've achieved in your test project above. I've downloaded it but can't see where such code is?
@lozflan yes if I remember correctly in that project I'm using the Timestamp
and not the Date
object.
I can't work out how to do this. If i have an model IDRequest as follows
Struct IDRequest: Codable { var uid: String? var lastUpdated: Timestamp? }
and then i want to create an instance like so
let idRequest = IDRequest(uid: "abc", lastUpdated: FieldValue.serverTimestamp())
so that firestore later creates the timestamp, not the client.
and then encoding with
var docData = try! FirestoreEncoder().encode(idRequest)
How do i get the compiler to stop complaining "Cannot convert value of type 'FieldValue' to expected argument type 'Timestamp?'
I don't want to have to munge the docData dictionary manually after creating it to add lastUpdated to it because doesn't that defeat the purpose of this whole "codable" exercise
@lozflan If you want to create a Timestamp
instance you need to use the appropriate constructors of the Timestamp
object. I recommend looking at the Timestamp
or FIRTimestamp
object and documentation. However, to make it easier you would need to do something like the following:
IDRequest(uid: "abc", lastUpdated: Timestamp.init(date: Date()))
@serjooo thx I've got it working with Timestamp but Ive got a strange problem with the extension line ie extension Timestamp: TimestampType {}. if i place this extension in my app everything works fine and I can encode and decode a test MyFirestoreModel object below. My app has a custom framework and if i move the Timestamp extension there, it encodes fine but crashes on decoding with following error message ...
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Foundation.Date, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "dateAdded", intValue: nil)], debugDescription: "Expected to decode Date but found FIRTimestamp instead.", underlyingError: nil))
There is no other change if i comment out the line extension Timestamp: TimestampType {} in my custom framework and uncomment the same line in my app, the myFirestoreModel encodes and decodes fine. If i do the opposite, the app crashes with the above error message.
struct MyFirestoreModel: Codable { let dateAdded: Timestamp }
My code (ignoring error checking)
` let myMod = MyFirestoreModel(dateAdded: Timestamp(date: Date())) let myData = try! FirestoreEncoder().encode(myMod)
db.collection("idRequests").document("mfm").setData(myData) { (err) in
self.db.collection("idRequests").document("mfm").getDocument(completion: { (snapshot, err) in
let dic = snapshot?.data()
print("dic:\(dic!)")
let myModelReconstituted = try! FirestoreDecoder().decode(MyFirestoreModel.self, from: dic!) // CRASHES here if extension Timestamp: TimestampType {} declared in custom framework
})
}`
@lozflan I'm not sure if this might be it, but did you try making the extension public so that your project itself can also use that extension?
Hey lads, In my Firestore data I keep timestamp and geopoint fields. In my Swift model I keep them like this:
When I use FirebaseDecoder though I get these errors:
Timestamp
Geopoint
Any ideas what I am doing wrong? Cheers