Home > Back-end >  How can I create a reusable add snapshot listener function for decodable data?
How can I create a reusable add snapshot listener function for decodable data?

Time:07-24

In my data model class I add four snapshot listeners to listen to document changes. I noticed that I am duplicating the code for each collection and only changing where the data is being stored and the data type when I add snapshot listener. How can I eliminate this duplication in an elegant way?

class Model: ObservableObject {
    @Published var tasks: [Task] = []
    @Published var notes: [Note] = []
    @Published var projects: [Project] = []
    @Published var notebooks: [Notebook] = []
    
    private var db = Firestore.firestore()
    
    init() {
        let _ = db.collection("tasks").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "")
            .addSnapshotListener { querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("No documents in 'tasks' collection")
                    return
                }
                self.tasks = documents.compactMap { queryDocumentSnapshot in
                    try? queryDocumentSnapshot.data(as: Task.self)
                }
            }
        let _ = db.collection("projects").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "")
            .addSnapshotListener { querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("No documents in 'projects' collection")
                    return
                }
                self.projects = documents.compactMap { queryDocumentSnapshot in
                    try? queryDocumentSnapshot.data(as: Project.self)
                }
            }
        let _ = db.collection("notes").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "")
            .addSnapshotListener { querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("No documents in 'notes' collection")
                    return
                }
                self.notes = documents.compactMap { queryDocumentSnapshot in
                    try? queryDocumentSnapshot.data(as: Note.self)
                }
            }
        let _ = db.collection("notebooks").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "")
            .addSnapshotListener { querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("No documents in 'notebooks' collection")
                    return
                }
                self.notebooks = documents.compactMap { queryDocumentSnapshot in
                    try? queryDocumentSnapshot.data(as: Notebook.self)
                }
            }
    }
}

I thought about creating an enum of my collections on the database however I am unclear as to how I would determine how to map to the correct data type and retrieve the correct array for storage without a verbose if statement.

enum Collection: String, CaseIterable {
    case tasks = "tasks",
    case notes = "notes",
    case projects = "projects",
    case notebooks = "notebooks"
}

for collection in Collection.allCases {
    // Add snapshot listener
}

CodePudding user response:

you could try something like this approach (untested, I don't have Firestore or your models), using a completion handler. Adjust the example code to suit your needs.

enum Collection: String, CaseIterable {
    case tasks = "tasks"
    case notes = "notes"
    case projects = "projects"
    case notebooks = "notebooks"
}

class Model: ObservableObject {
    @Published var tasks: [Task] = []
    @Published var notes: [Note] = []
    @Published var projects: [Project] = []
    @Published var notebooks: [Notebook] = []
    
    private var db = Firestore.firestore()
    
    func collect<T>(col: Collection, handler: @escaping ([T]) -> Void) { // <-- here
        let _ = db.collection(col.rawValue).whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "")
            .addSnapshotListener { querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("No documents in collection: \(col.rawValue)")
                    return handler([]) // <-- here
                }
                documents.compactMap { queryDocumentSnapshot in
                    let results = try? queryDocumentSnapshot.data(as: T.self)
                    handler(results) // <-- here
                }
            }
    }

    init() {
        collect(col: .tasks) { self.tasks = $0 }
        collect(col: .projects) { self.projects = $0 }
        collect(col: .notebooks) { self.notebooks = $0 }
        collect(col: .notes) { self.notes = $0 }
    }
}
  • Related