You're almost there - using Codable
for your data structs is the first step.
Instead of mapping your documents manually (as you do), Codable
allows you to rely on the Firestore SDK and the Swift compiler to do the heavy lifting for you. I've written a comprehensive guide about how this works and how to use it, including code snippets for fetching single documents and entire collections or queries - check it out: Mapping Firestore Data in Swift - The Comprehensive Guide.
In your case, here is a modified version of the code for your repository (I wouldn't recommend putting this code Auth.swift
, as the main concern is not about auth, but fetching data):
class RoomRepository: ObservableObject {
@Published var rooms = [Room]()
@Published var user: User
@Published var errorMessage: String?
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("rooms")
.whereField("members", arrayContains: user.uid)
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.errorMessage = "No documents in 'colors' collection"
return
}
self.rooms = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: Room.self) }
switch result {
case .success(let room):
if let room = room {
// A Room value was successfully initialized from the DocumentSnapshot.
self?.errorMessage = nil
return room
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
self?.errorMessage = "Document doesn't exist."
return nil
}
case .failure(let error):
// A Room value could not be initialized from the DocumentSnapshot.
self?.errorMessage = error.localizedDescription
return nil
}
}
}
}
}
}
A couple of notes:
- You wrapped the code for fetching data from Firestore in
DispatchQueue.main.async
. Instead of wrapping the entire block, you should only wrap the code that accessed the UI (i.e. the code inside the completion block).
- Since Firestore returns on the main thread anyway, do no not need to switch back to the main thread yourself. See this thread for more details about this.
Codable is a very powerful tool, have a look at my blog post to learn about all the different ways you can use it to map data - I cover a wide range of use cases and data types. If you find anything is missing, let me know (by filing a feature request here) and I will add it. There's also a GitHub repo with all the source code: https://github.com/peterfriese/Swift-Firestore-Guide