Strategy for handling server-provided values at creation time
Hi! First of all: thanks so much for making this!
So, I've got a (fairly common, I'd imagine) case in an app where I'm creating a model locally and storing it in Firebase, but it has a timestamp field that's set by the server at creation-time rather than the client.
In vanilla Firebase, it's easy enough to do this by setting the field equal to the placeholder ServerValue.timestamp(), which gets silently set on the server.
With CodableFirebase, here's what my code looks like (ignoring proper error handling/etc):
value = MyCustomClass(timestamp: Date()) // This placeholder Date object will get thrown out
var fbValue = try! FirebaseEncoder().encode(value) as! NSDictionary
fbValue.setValue(ServerValue.timestamp(), forKey: "timestamp")
ref.setValue(fbValue)
This more or less works, but having to manually munge my encoded dictionary makes me sad. I'm curious if anyone has found a better pattern for handling placeholder server-provided data in a way that doesn't expose the root Codable model object to the implementation detail of using Firebase.
(Apologies if GH Issues isn't the appropriate place for this, but figured it was more likely anyone actually using CodableFirebase would see this here than on e.g. StackOverflow)
Hi @lazerwalker! Thanks for reaching out! There are several ways you can try to solve your problem. First of all, because ServerValue.timestamp() returns a dictionary, we can kinda overcome it the following:
struct ServerValueTest: Codable {
enum Keys: CodingKey {
case date
}
var date: Date? = nil
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
if let date = date {
try container.encode(date, forKey: .date)
} else {
let dict = ServerValue.timestamp().reduce(into: [:], { $0[$1.key.base as! String] = $1.value as? String })
try container.encode(dict, forKey: .date)
}
}
}
If you start using in a lot of places, I would even create something like this:
enum FirebaseDate: Encodable {
case date(Date)
case serverTimestamp
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .date(date):
try container.encode(date)
case .serverTimestamp:
let dict = ServerValue.timestamp().reduce(into: [:], { $0[$1.key.base as! String] = $1.value as? String })
try container.encode(dict)
}
}
}
extension Optional where Wrapped == Date {
var firebaseDate: FirebaseDate {
switch self {
case let .some(date): return .date(date)
case .none: return .serverTimestamp
}
}
}
and then our model will become something like the following:
struct ServerValueTest: Codable {
enum Keys: CodingKey {
case date
}
var date: Date? = nil
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
try container.encode(date.firebaseDate, forKey: .date)
}
}
I hope this helps :) If not, please let me know
@lazerwalker so did the suggested approach help?
@alickbass I'm trying to get the timestamps right and I'm struggling a little bit. In your struct ServerValueTest do I really need to do the whole encoding by myself for every model object that has a timestamp? I don't want to put so much boilerplate code in there...