Handling Firebase asynchronous callbacks with Combine framework.
To run the example project, clone the repo, and run pod install from the Example directory first. See Usage below for more information.
- Xcode 11.3+ | Swift 5.1+
- iOS 13.0+ | tvOS 13.0+ | macOS 10.15+
CombineFirebase is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod'CombineFirebase/Firestore'pod'CombineFirebase/RemoteConfig'pod'CombineFirebase/Database'pod'CombineFirebase/Storage'pod'CombineFirebase/Auth'pod'CombineFirebase/Functions'CombineFirebase is available through Swift Package Manager in beta status. To install it, you must have Xcode 12.0 and above, simply add CombineFirebase to an existing Xcode project as a package dependency:
- From the File menu, select Swift Packages > Add Package Dependency...
- Enter https://github.com/rever-ai/CombineFirebase into the package repository URL text field.
- Xcode should choose updates package up to the next version option by default.
import CombineFirebase
import Firebase
import CombineBasic write operation
varcancelBag=Set<AnyCancellable>()func setUserData(){letref=Database.database().reference()
ref.child("users").child("1").setValue(["username":"Arnonymous"]).sink{ _ inprint("Document successfully updated")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#basic_writeListen for value events
varcancelBag=Set<AnyCancellable>()func listenForValueEvent(){letref=Database.database().reference()
ref.child("users").child("1").publisher(.value).receive(on:RunLoop.main).sink{ snapshot inprint("Value:\(snapshot.value)")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#listen_for_value_eventsRead data once
varcancelBag=Set<AnyCancellable>()func readDataOnce(){letref=Database.database().reference()
ref.child("users").child("1").observeSingleEvent(.value).receive(on:RunLoop.main).sink{ snapshot inprint("Value:\(snapshot.value)")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#read_data_onceUpdate specific fields
varcancelBag=Set<AnyCancellable>()func updateFields(){letref=Database.database().reference()letchildUpdates=["/posts/\(key)": post,"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates).receive(on:RunLoop.main).sink{ _ in
// Success
}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#update_specific_fieldsDelete data
varcancelBag=Set<AnyCancellable>()func deleteData(){letref=Database.database().reference()
ref.removeValue().receive(on:RunLoop.main).sink{ _ in
// Success
}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#delete_dataSave data as transactions
varcancelBag=Set<AnyCancellable>()func saveDataAsTransaction(){letref=Database.database().reference()
ref.runTransactionBlock{ currentData in
// TransactionResult
}.sink{ _ in
// Success
}.store(in:&cancelBag)}
// https://firebase.google.com/docs/database/ios/read-and-write#save_data_as_transactionsSetting data
varcancelBag=Set<Cancellable>()letdb=Firestore.firestore()structCity:Codable{varname:String?=nilvarstate:String?=nilvarcountry:String?=nilvarcapital:String?=nilvarpopulation:Int?=nil
// local variable varid:String?=nil}func setSanFranciscoData(city:City){letonErrorCompletion:((Subscribers.Completion<Error>)->Void)?={ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}letonValue:(Void)->Void={print("✅ value")}
// Add a new document in collection "cities"
(db.collection("cities").document("SF").setData(from: city)asAnyPublisher<Void,Error>) // Note: you can use (as Void) for simple setData({})
.sink(receiveCompletion: onErrorCompletion, receiveValue: onValue).store(in:&cancelBag)}
// Add a new document with a generated id.
func addSanFranciscoDocument(city:City){(db.collection("cities").addDocument(data:["name":"San Francisco","state":"CA","country":"USA","capital":false,"population":860000])asAnyPublisher<DocumentReference,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}){ ref inprint("Document added with ID: \(ref.documentID)")}.store(in:&cancelBag)}
// Set the "capital" field of the city 'SF'
func updateSanFranciscoDocument(){(db.collection("cities").document("SF").updateData(["capital":true])asAnyPublisher<Void,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print(i"❗️ failure: \(error)")}){ _ in}.store(in:&cancelBag)}
// https://firebase.google.com/docs/firestore/manage-data/add-dataGet a document
func getDocument(){
db.collection("cities").document("SF").getDocument().sink(receiveCompletion:{(completion)inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ document inprint("Document data: \(document.data())")}.store(in:&cancelBag)}func getDocumentAsObject(){
db.collection("cities").document("SF").getDocument(as:City.self).sink(receiveCompletion:{(completion)inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ city inprint("City: \(city)")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/firestore/query-data/get-dataGet Realtime updates
letdb=Firestore.firestore()
// Document
func listenDocument(){
db.collection("cities").document("SF").publisher().sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ document inprint("Document data: \(document.data())")}.store(in:&cancelBag)}varcityDocumentSnapshotMapper:(DocumentSnapshot)throws->City?{{varcity=try $0.data(as:City.self)
city.id = $0.documentID
return city
}}func listenDocumentAsObject(){
db.collection("cities").document("SF").publisher(as:City.self, documentSnapshotMapper: cityDocumentSnapshotMapper).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ city inprint("City: \(city)")}.store(in:&cancelBag)}
// Collection
func listenCollection(){
db.collection("cities").publisher().sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ snapshot inprint("collection data: \(snapshot.documents)")}.store(in:&cancelBag)}func listenCollectionAsObject(){
db.collection("cities").publisher(as:City.self, documentSnapshotMapper: cityDocumentSnapshotMapper).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ cities inprint("Cities: \(cities)")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/firestore/query-data/listenBatched writes
varcancelBag=Set<AnyCancellable>()func batchWrite(){letdb=Firestore.firestore()
// Get new write batch
letbatch= db.batch()
// Update the population of 'SF'
letsfRef= db.collection("cities").document("SF")
batch.updateData(["population":1000000], forDocument: sfRef)
// Commit the batch
batch.commit().sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ _ in}.store(in:&cancelBag)}
// https://firebase.google.com/docs/firestore/manage-data/transactionsTransactions
varcancelBag=Set<AnyCancellable>()func transaction(){letdb=Firestore.firestore()letsfReference= db.collection("cities").document("SF")(db.runTransaction{ transaction inletsfDocument=try transaction.getDocument(sfReference)guardlet oldPopulation = sfDocument.data()?["population"]as?Intelse{leterror=NSError(
domain:"AppErrorDomain",
code:-1,
userInfo:[
NSLocalizedDescriptionKey:"Unable to retrieve population from snapshot \(sfDocument)"])throw error
}
transaction.updateData(["population": oldPopulation +1], forDocument: sfReference)returnnil}asAnyPublisher<Any?,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ _ inprint("Transaction successfully committed!")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/firestore/manage-data/transactionsFetch
// TimeInterval is set to expirationDuration here, indicating the next fetch request will use
// data fetched from the Remote Config service, rather than cached parameter values, if cached
// parameter values are more than expirationDuration seconds old. See Best Practices in the
// README for more information.
varcancelBag=Set<AnyCancellable>()func fetchRemoteConfig(){(RemoteConfig.remoteConfig().fetch(withExpirationDuration:TimeInterval(expirationDuration), activateFetched:true)asAnyPublisher<RemoteConfigFetchStatus,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("❗️ failure: \(error)")}}){ status inprint("Config fetched! with success:\(status ==.success)")}.store(in:&cancelBag)}
// https://firebase.google.com/docs/remote-config/iosUpload
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")letdata:Data // Upload data
(reference.putData(data)asAnyPublisher<StorageMetadata,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ metadata in
// Success }.store(in:&cancelBag)letfileURL:URL // Upload file
(reference.putFile(from: fileURL)asAnyPublisher<StorageMetadata,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ metadata in
// Success }.store(in:&cancelBag)Observe events
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")letfileURL:URL // Upload file
letuploadTask= reference.putFile(from: fileURL)
// Listen for state changes
uploadTask.publisher(.progress).sink(receiveCompletion:{ _ inprint("🏁 finished")}){ snapshot iniflet error = snapshot.error {print("error: \(error)")}
// Upload reported progress
letpercentComplete=100.0* Double(snapshot.progress?.completedUnitCount ??0)/ Double(snapshot.progress.totalUnitCount ??1)}.store(in:&cancelBag)Download
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
(reference.getData(maxSize:1*1024*1024)asAnyPublisher<Data,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ data in
// Data for "images/space.jpg" is returned
}.store(in:&cancelBag)
// Create local filesystem URL
letlocalURL=URL(string:"path/to/image")!
// Download to the local filesystem
(reference.write(toFile: localURL)asAnyPublisher<URL,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ data in
// Local file URL for "images/space.jpg" is returned
}.store(in:&cancelBag)URL
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")
// Fetch the download URL
(reference.downloadURL()asAnyPublisher<URL,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ data in
// Get the download URL for 'images/space.jpg' }.store(in:&cancelBag)Metadata
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")
// Create file metadata to update
letnewMetadata=StorageMetadata()
// Update metadata properties
(reference.updateMetadata(newMetadata)asAnyPublisher<StorageMetadata,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ metadata in
// Updated metadata for 'images/space.jpg' is returned }.store(in:&cancelBag)
// Get metadata properties
(reference.getMetadata()asAnyPublisher<StorageMetadata,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ metadata in
// Metadata now contains the metadata for 'images/space.jpg'
}.store(in:&cancelBag)Delete
varcancelBag=Set<AnyCancellable>()letreference=Storage.storage().reference(forURL:"\(your_firebase_storage_bucket)/images/space.jpg")
// Delete the file
(reference.delete()asAnyPublisher<Void,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ _ in
// File deleted successfully }.store(in:&cancelBag)Create
varcancelBag=Set<AnyCancellable>()letauth=Auth.auth()
// Create a password-based account
(auth.createUser(withEmail:"xxx@xxx.com", password:"1q2w3e4r")asAnyPublisher<AuthDataResult,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ _ in
// User signed in
}.store(in:&cancelBag)
// https://firebase.google.com/docs/auth/ios/password-authSign in
varcancelBag=Set<AnyCancellable>()letauth=Auth.auth()
// Sign in a user with an email address and password
(auth.signIn(withEmail:"xxx@xxx.com", password:"1q2w3e4r")asAnyPublisher<AuthDataResult,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ _ in
// User signed in
}.store(in:&cancelBag)
// https://firebase.google.com/docs/auth/ios/password-authUpdate email
varcancelBag=Set<AnyCancellable>()letuser=Auth.auth().currentUser
// Set a user's email address
(user.updateEmail(to:"xxx@xxx.com")asAnyPublisher<Void,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ _ in
// Completed updating Email }.store(in:&cancelBag)
// https://firebase.google.com/docs/auth/ios/manage-usersDelete
varcancelBag=Set<AnyCancellable>()letuser=Auth.auth().currentUser
// Delete a user
(user.delete()asAnyPublisher<Void,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error): // Uh-oh, an error occurred! }}){ _ in
// User deleted
}.store(in:&cancelBag)
// https://firebase.google.com/docs/auth/ios/manage-usersvarcancelBag=Set<AnyCancellable>()letfunctions=Functions.functions()letrequest= functions.httpsCallable("functionName")(request
.call(["parameter":"value"])asAnyPublisher<HTTPSCallableResult,Error>).sink(receiveCompletion:{ completion inswitch completion {case.finished:print("🏁 finished")case.failure(let error):print("error:\(error)")}}){ result inprint("response:\(result)")}.store(in:&cancelBag)
// https://firebase.google.com/docs/functions/callable#call_the_functionKumar Shivang, shivang.iitk@gmail.com
CombineFirebase is available under the MIT license. See the LICENSE file for more info.