Skip to content

Repository files navigation

CombineFirebase

SwiftPM compatibleVersionLicensePlatform

Handling Firebase asynchronous callbacks with Combine framework.

Example

To run the example project, clone the repo, and run pod install from the Example directory first. See Usage below for more information.

Requirements

  • Xcode 11.3+ | Swift 5.1+
  • iOS 13.0+ | tvOS 13.0+ | macOS 10.15+

Installation

CocoaPods

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'

Swift Package Manager

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:

  1. From the File menu, select Swift Packages > Add Package Dependency...
  2. Enter https://github.com/rever-ai/CombineFirebase into the package repository URL text field.
  3. Xcode should choose updates package up to the next version option by default.

Usage

import CombineFirebase
import Firebase
import Combine

Database

Basic 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_write

Listen 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_events

Read 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_once

Update 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_fields

Delete 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_data

Save 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_transactions

Firestore

Setting 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-data

Get 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-data

Get 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/listen

Batched 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/transactions

Transactions

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/transactions

RemoteConfig

Fetch

// 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/ios

Storage

Upload

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)

Auth

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-auth

Sign 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-auth

User

Update 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-users

Delete

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-users

Functions

varcancelBag=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_function

Author

Kumar Shivang, shivang.iitk@gmail.com

License

CombineFirebase is available under the MIT license. See the LICENSE file for more info.

About

Combine wrapper on Google's iOS Firebase library.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages