Get rid of redundant Firebase code with FireThel! You can do CRUD operations in Firestore with a single line:
let reference = try await db.addDoc(model: restaurant, path: .getPath(for: .restaurant))
- Xcode 14.1+ | Swift 5.7+
- iOS 13.0+
-
Add the library with Swift Package Manager. Choose FireThelFirestore.
-
Take advantage of the factory pattern and make your paths less error prone and more syntactically pleasing!
// MARK: - Factory for Firestore collection path extension String { enum CollectionPath { case restaurant case menu(restaurantId: String) } // Factory static func getPath(for path: CollectionPath) -> String { switch path { case .restaurant: return "restaurant" case .menu(let restaurantId): return "restaurant/\(restaurantId)/menu" } } }
-
Make your data model conform to
Codable
and add the@DocumentID
property wrapper.import FirebaseFirestoreSwift struct RestaurantMenu: Codable { @DocumentID var id: String? var specialDish: String var desert: String }
-
Import the library with Firestore to create an instance of the database.
import FireThelFirestore import FirebaseFirestore class SomeClass { private let db = Firestore.firestore() }
-
Perform CRUD operations.
let restaurant = Restaurant(name: name, type: RestaurantType(rawValue: restaurantType)!) let menu = RestaurantMenu(specialDish: specialDish, desert: desert) // add(), if you want Firebase to take care of the id let reference = try await db.addDoc(model: restaurant, path: .getPath(for: .restaurant)) // create(), if you want to update or set a doc with a custom id try await db.createDoc( model: menu, path: .getPath(for: .menu(restaurantId: reference.documentID)), documentId: reference.documentID )
// get single let menu: RestaurantMenu = try await db.getDoc(path: .getPath(for: .menu(restaurantId: documentId)), documentId: documentId) // get all from collection let restaurants: [Restaurant] = try await db.getDocs(predicates: [.limit(to: 5)], path: .getPath(for: .restaurant))
listenerSubscription = db.observeCollection(path: .getPath(for: .restaurant), predicates: [.isEqualTo("type", "American")]) .sink { completiom in switch completiom { case .finished: print("Finished") case .failure(let err): print("Listener err: \(err)") } } receiveValue: { [weak self] output in self?.restaurantsView.restaurants = output }
private func listen() { listenerSubscription = db.observeCollection(path: .getPath(for: .restaurant), predicates: [.isEqualTo("type", "American")]) .sink { completiom in } receiveValue: { [weak self] output in // Output need to match listener output. // With a function it's very clear what the generic type will be, and the compiler will compile self?.handleOutput(output) } } private func handleOutput(_ listenerOutputs: [ListenerOuput<Restaurant>]) { for output in listenerOutputs { switch output.changeType { case .added: print("New doc added") case .modified: print("Doc changed") case .removed: print("Doc removed") } } }
let fields: [AnyHashable: Any] = [ "name": name, "type": restaurantType ] try await db.updateDoc(fields, path: .getPath(for: .restaurant), documentId: documentId)
try await db.deleteDoc(path: .getPath(for: .restaurant), documentId: documentId)