Skip to content

Instantly share code, notes, and snippets.

@alexanderwe
Created January 27, 2020 15:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexanderwe/ce29472a7f05e01f2220833dfcc0071f to your computer and use it in GitHub Desktop.
Save alexanderwe/ce29472a7f05e01f2220833dfcc0071f to your computer and use it in GitHub Desktop.
Firebase + Combine
import Firebase
import Combine
// MARK: - CollectionReference
public struct CombineFIRCollection {
fileprivate let collection: CollectionReference
}
extension CombineFIRCollection {
public final class Subscription<S: Subscriber>: Combine.Subscription where S.Input == QuerySnapshot, S.Failure == Error {
private var subscriber: S?
private let collection: CollectionReference
private let _cancel: () -> Void
fileprivate init(subscriber: S,
collection: CollectionReference,
addListener: @escaping (CollectionReference, @escaping (QuerySnapshot?, Error?) -> Void) -> ListenerRegistration,
removeListener: @escaping (ListenerRegistration) -> Void) {
self.subscriber = subscriber
self.collection = collection
// This is the strong reference for an ListenerRegistration from Firebase
let listener = addListener(collection) { querySnapshot, error in
if let error = error {
subscriber.receive(completion: .failure(error))
} else if let querySnapshot = querySnapshot {
_ = subscriber.receive(querySnapshot)
}
}
// This a method that will get the current collection reference and the listener registration as input
self._cancel = { removeListener(listener) }
}
public func request(_ demand: Subscribers.Demand) {}
public func cancel() {
_cancel()
subscriber = nil
}
}
public struct Publisher: Combine.Publisher {
public typealias Output = QuerySnapshot
public typealias Failure = Error
private let collection: CollectionReference
private let addListener: (CollectionReference, @escaping (QuerySnapshot?, Error?) -> Void) -> ListenerRegistration
private let removeListener: (ListenerRegistration) -> Void
init(collection: CollectionReference,
addListener: @escaping (CollectionReference, @escaping (QuerySnapshot?, Error?) -> Void) -> ListenerRegistration,
removeListener: @escaping (ListenerRegistration) -> Void) {
self.collection = collection
self.addListener = addListener
self.removeListener = removeListener
}
public func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output {
let subscription = Subscription(subscriber: subscriber,
collection: collection,
addListener: addListener,
removeListener: removeListener)
subscriber.receive(subscription: subscription)
}
}
}
extension CollectionReference {
public var combine: CombineFIRCollection {
return CombineFIRCollection(collection: self)
}
}
extension CombineFIRCollection {
public func snapshotPublisher() -> AnyPublisher<QuerySnapshot, Error> {
return Publisher(collection: collection,
addListener: { $0.addSnapshotListener($1) }, // $0 is the collection reference, $1 is the method that is called when the listener is fired
removeListener: { $0.remove() }
).eraseToAnyPublisher()
}
}
// MARK: - DocumentReference
public struct CombineFIRDocument {
fileprivate let document: DocumentReference
}
extension CombineFIRDocument {
public final class Subscription<S: Subscriber>: Combine.Subscription where S.Input == DocumentSnapshot, S.Failure == Error {
private var subscriber: S?
private let document: DocumentReference
private let _cancel: () -> Void
fileprivate init(subscriber: S,
document: DocumentReference,
addListener: @escaping (DocumentReference, @escaping (DocumentSnapshot?, Error?) -> Void) -> ListenerRegistration,
removeListener: @escaping (ListenerRegistration) -> Void) {
self.subscriber = subscriber
self.document = document
// This is the strong reference for an ListenerRegistration from Firebase
// Here we pipe the "messages" from Firebase to our subscriber
let listener = addListener(document) { documentSnapshot, error in
if let error = error {
subscriber.receive(completion: .failure(error))
} else if let documentSnapshot = documentSnapshot {
_ = subscriber.receive(documentSnapshot)
}
}
// This a method that will get the current collection reference and the listener registration as input
self._cancel = { removeListener(listener) }
}
public func request(_ demand: Subscribers.Demand) {}
public func cancel() {
_cancel()
subscriber = nil
}
}
public struct Publisher: Combine.Publisher {
public typealias Output = DocumentSnapshot
public typealias Failure = Error
private let document: DocumentReference
private let addListener: (DocumentReference, @escaping (DocumentSnapshot?, Error?) -> Void) -> ListenerRegistration
private let removeListener: (ListenerRegistration) -> Void
init(document: DocumentReference,
addListener: @escaping (DocumentReference, @escaping (DocumentSnapshot?, Error?) -> Void) -> ListenerRegistration,
removeListener: @escaping (ListenerRegistration) -> Void) {
self.document = document
self.addListener = addListener
self.removeListener = removeListener
}
public func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output {
let subscription = Subscription(subscriber: subscriber,
document: document,
addListener: addListener,
removeListener: removeListener)
subscriber.receive(subscription: subscription)
}
}
}
extension DocumentReference {
public var combine: CombineFIRDocument {
return CombineFIRDocument(document: self)
}
}
extension CombineFIRDocument {
public func snapshotPublisher() -> AnyPublisher<DocumentSnapshot, Error> {
return Publisher(document: document,
addListener: { $0.addSnapshotListener($1) }, // $0 is the document reference, $1 is the method that is called when the listener is fired
removeListener: { $0.remove() }
).eraseToAnyPublisher()
}
}
Firestore.firestore()
.collection("Path")
.combine
.snapshotPublisher()
.eraseToAnyPublisher()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment