Created
January 27, 2020 15:30
-
-
Save alexanderwe/ce29472a7f05e01f2220833dfcc0071f to your computer and use it in GitHub Desktop.
Firebase + Combine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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