Saving Firestore model with @DocumentID to UserDefaults in swift

ALex

I'm trying to save a firebase model to user defaults but the coding/decoding of the id field fails.

struct DeviceModel: Identifiable, Codable, Hashable {
    
    @DocumentID var id: String?
    var fcmToken: String
    var userId: String
    @ServerTimestamp var expiresOn: Timestamp?
    
    enum CodingKeys: String, CodingKey {
        case id
        case fcmToken = "token"
        case expiresOn = "expires_on"
        case userId = "user_id"
    }
    
    init(id: String? = nil,
         fcmToken: String,
         userId: String,
         expiresOn: Timestamp? = Timestamp()) {
        self.id = id
        self.fcmToken = fcmToken
        self.userId = userId
        self.expiresOn = expiresOn
    }
    
}

UserDefault extension

extension UserDefaults {
    func decode<T: Decodable>(_ type: T.Type, forKey defaultName: String) throws -> T {
//        try! JSONDecoder().decode(T.self, from: data(forKey: defaultName) ?? .init())
        try! Firestore.Decoder().decode(T.self, from: data(forKey: defaultName) ?? .init())
    }
    func encode<T: Encodable>(_ value: T, forKey defaultName: String) throws {
//        try! set(JSONEncoder().encode(value), forKey: defaultName)
        try! set(Firestore.Encoder().encode(value), forKey: defaultName)
    }
}

At first i tried with JSONEncoder and got an error saying that @DocumentID should be encoded/decoded using firestore encoder/decoder.

Fatal error: 'try!' expression unexpectedly raised an error: 
FirebaseFirestoreSwift.FirestoreEncodingError.encodingIsNotSupported("DocumentID values can only be encoded with Firestore.Encoder")

After that, i use them as above but now i get

2023-04-18 10:52:19.298750+0300 Voxic[49960:1461668] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object {
    "expires_on" = "<FIRTimestamp: seconds=1685555885 nanoseconds=220984000>";
    token = "<token>";
    "user_id" = 41giBVERER34QNPFZmlB3;
} for key localDeviceKey'

One thing i find strange is that the user_id is not printed as a string in the above error. It might be just that, a printing issue though.

Any thoughts? Thank you

byaruhaf

Try using a Custom encode function & Custom init(from:) function

  • Custom encode function that encodes the DocumentID property using the Firestore.Encoder
  • Custom init(from:) function that decodes the DocumentID property using the Firestore.Decoder
struct DeviceModel: Identifiable, Codable, Hashable {
    @DocumentID var id: String?
    var fcmToken: String
    var userId: String
    @ServerTimestamp var expiresOn: Timestamp?
    
    enum CodingKeys: String, CodingKey {
        case id
        case fcmToken = "token"
        case expiresOn = "expires_on"
        case userId = "user_id"
    }
    
    init(id: String? = nil,
         fcmToken: String,
         userId: String,
         expiresOn: Timestamp? = Timestamp()) {
        self.id = id
        self.fcmToken = fcmToken
        self.userId = userId
        self.expiresOn = expiresOn
    }
    
    // Custom init(from:) function that decodes the id property using the Firestore.Decoder
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        fcmToken = try container.decode(String.self, forKey: .fcmToken)
        userId = try container.decode(String.self, forKey: .userId)
        expiresOn = try container.decode(Timestamp?.self, forKey: .expiresOn)
        
        // Decode the DocumentID property using the Firestore.Decoder
        if let idContainer = try? decoder.container(keyedBy: CodingKeys.self),
           let id = try? idContainer.decode(String.self, forKey: .id) {
            self.id = id
        }
    }
    
    // Custom encode function that encodes the DocumentID property using the Firestore.Encoder
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(fcmToken, forKey: .fcmToken)
        try container.encode(userId, forKey: .userId)
        try container.encode(expiresOn, forKey: .expiresOn)
        
        // Encode the DocumentID property using the Firestore.Encoder
        if let id = id {
            let firestoreEncoder = Firestore.Encoder()
            let something = try firestoreEncoder.encode(["id":id])
            
            if let mapId = something["id"] as? String {
                try container.encode(mapId, forKey: .id)
            }
        }
    }
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related