alickbass / CodableFirebase

Use Codable with Firebase

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Could not cast value of type 'NSNull' to 'FIRTimestamp'

Tulleb opened this issue · comments

I am facing this error while trying to decode an object through FirebaseDecoder containing a timestamp:

Could not cast value of type 'NSNull' (0x1db8afc00) to 'FIRTimestamp' (0x1db8b1f20).
2022-01-04 10:54:09.430366-0300 Voila[8602:2189885] Could not cast value of type 'NSNull' (0x1db8afc00) to 'FIRTimestamp' (0x1db8b1f20).
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

This happens at the line try? values.decode(FirebaseFirestore.Timestamp.self, forKey: .lastUpdate) from the User.swift file, crashing when calling the decoded = value as! T line 1234 from the Decoder.swift file of this repository.

The lastUpdate is a Swift Date object which is encoded using FieldValue.serverTimestamp().

The Firestore User object currently has a timestamp date properly filled:
Screenshot 2022-01-04 at 12 56 32


Here are some extracts of my code:

NetworkManager.swift:

    func loadUser(from uuid: String, completion: @escaping (Result<User, LoadingError>) -> Void) {
        let docRef = database.collection("users").document(uuid)

        docRef.getDocument { (document, error) in
            if let error = error {
                print("Error while fetching user: \(error)")
                error.sendToCrashlytics()
                completion(.failure(.unknown))
                return
            }

            guard let document = document,
                  document.exists,
                  let data = document.data() else {
                print("User \(uuid) does not exist")
                completion(.failure(.invalidUUID))
                return
            }

            do {
                let user = try FirestoreDecoder().decode(User.self, from: data)
                print("User received: \(user)")
                completion(.success(user))
            } catch {
                print("Error while unboxing user \(uuid): \(error)")
                error.sendToCrashlytics()
                completion(.failure(.outOfDate))
            }
        }
    }

    func save(user: User, completion: ((SavingError?) -> Void)? = nil) {
        print("Saving user remotely...")

        let ref = database.document("users/\(user.uuid)")
        let userDictionary = formatForFirebase(dictionary: user.dictionary, device: true)

        ref.setData(userDictionary) { (error) in
            if let error = error {
                print("Error uploading user document \(user.uuid): \(error)")
                error.sendToCrashlytics()
                completion?(.unknown)
                return
            }

            print("User successfully saved!")
            completion?(nil)
        }
    }

    func formatForFirebase(dictionary: [String: Any], lastUpdate: Bool = true, device: Bool = false) -> [String: Any] {
        var bufferDictionary = dictionary

        addMiscInformations(dictionary: &bufferDictionary, lastUpdate: lastUpdate, device: device)

        return replaceNilWithDelete(from: bufferDictionary)
    }

    func addMiscInformations(dictionary: inout [String: Any], lastUpdate: Bool, device: Bool) {
        if lastUpdate {
            dictionary["lastUpdate"] = FieldValue.serverTimestamp()
        }

        if device {
            dictionary["device"] = Device.current.dictionary
        }
    }

User.swift:

import Foundation
import FirebaseFirestore

final class User: Codable {

    var uuid: String
    var nickname: String?
    [...]
    var lastUpdate: Date

    enum CodingKeys: String, CodingKey {
        case uuid
        case nickname
        [...]
        case lastUpdate
    }

    var dictionary: [String: Any] {
        var dictionary: [String: Any] = [:]

        dictionary[CodingKeys.uuid.rawValue] = uuid
        dictionary[CodingKeys.nickname.rawValue] = nickname
        [...]

        return dictionary
    }

    init(uuid: String) {
        self.uuid = uuid
        nickname = nil
        [...]
        lastUpdate = Date()
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        uuid = try values.decode(String.self, forKey: .uuid)
        nickname = try? values.decode(String.self, forKey: .nickname)
       [...]

        if let lastUpdateTimestamp = try? values.decode(FirebaseFirestore.Timestamp.self, forKey: .lastUpdate) {
            lastUpdate = lastUpdateTimestamp.dateValue()
        } else {
            lastUpdate = Date()
        }
    }
}

Using Timestamp+Extension.swift alongside:

import Foundation
import FirebaseFirestore
import CodableFirebase

extension Timestamp: TimestampType {}

Extract of Podfile:

	pod 'CodableFirebase', '~> 0.2'
	pod 'Firebase/Analytics', '~> 8.6'
	pod 'Firebase/Auth', '~> 8.6'
	pod 'Firebase/Crashlytics', '~> 8.6'
	pod 'Firebase/Firestore', '~> 8.6'
	pod 'Firebase/Functions', '~> 8.6'
	pod 'Firebase/Messaging', '~> 8.6'
	pod 'Firebase/RemoteConfig', '~> 8.6'
	pod 'Firebase/Storage', '~> 8.6'
	pod 'FirebaseFirestoreSwift', '~> 8.6-beta'

Thank you a lot for your help on this.

I just saw that this lib was not supported anymore and is using force cast (reason of the crash).

I am moving to FirebaseFirestoreSwift (https://peterfriese.dev/firestore-codable-the-comprehensive-guide) which is the official Firebase library for Swift guys using Codable.