Skip to content

Instantly share code, notes, and snippets.

@tera-ny
Last active March 27, 2023 22:15
Show Gist options
  • Save tera-ny/cf723f5857d2bdea36b331f9c1427c3d to your computer and use it in GitHub Desktop.
Save tera-ny/cf723f5857d2bdea36b331f9c1427c3d to your computer and use it in GitHub Desktop.
Swift + Combine + Firestore
import Foundation
import FirebaseFirestoreSwift
import FirebaseFirestore
import Combine
struct Document<Model: Codable> {
let ref: DocumentReference
let data: Model
static func get(collectionPath: String, id: String) -> Deferred<Future<Document<Model>, Error>> {
.init { () -> Future<Document<Model>, Error> in
let document = Firestore.firestore().collection(collectionPath).document(id)
return get(documentRef: document)
}
}
static func get(documentRef: DocumentReference) -> Deferred<Future<Document<Model>, Error>> {
.init { () -> Future<Document<Model>, Error> in
get(documentRef: documentRef)
}
}
static func listen(documentRef: DocumentReference) -> Deferred<FirestoreDocumentPublisher<Model>> {
.init { () -> FirestoreDocumentPublisher<Model> in
listen(documentRef: documentRef)
}
}
static func listen(query: Query) -> Deferred<FirestoreCollectionPublisher<Model>> {
.init { () -> FirestoreCollectionPublisher<Model> in
listen(query: query)
}
}
private static func get(documentRef: DocumentReference) -> Future<Document<Model>, Error> {
.init { observer in
documentRef.getDocument { (snapshot, error) in
if let error = error {
observer(.failure(error))
} else {
do {
let data = try snapshot!.data(as: Model.self, decoder: Firestore.Decoder())!
observer(.success(.init(ref: documentRef, data: data)))
} catch {
observer(.failure(error))
}
}
}
}
}
private static func get(query: Query) -> Future<[Document<Model>], Error> {
.init { observer in
query.getDocuments { (snapshot, error) in
if let error = error {
observer(.failure(error))
} else {
do {
let data = try snapshot!.documents.map { document -> Document<Model> in
let data = try document.data(as: Model.self, decoder: Firestore.Decoder())!
return .init(ref: document.reference, data: data)
}
observer(.success(data))
} catch {
observer(.failure(error))
}
}
}
}
}
private static func listen(documentRef: DocumentReference) -> FirestoreDocumentPublisher<Model> {
return .init(documentRef: documentRef)
}
private static func listen(query: Query) -> FirestoreCollectionPublisher<Model> {
return .init(query: query)
}
}
import Foundation
import FirebaseFirestore
import Combine
struct FirestoreCollectionPublisher<Model: Codable>: Publisher {
typealias Output = [Document<Model>]
typealias Failure = Error
let query: Query
init(query: Query) {
self.query = query
}
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let subscription = FirestoreSubscription(query: query, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
struct FirestoreDocumentPublisher<Model: Codable>: Publisher {
typealias Output = Document<Model>
typealias Failure = Error
let ref: DocumentReference
init(documentRef: DocumentReference) {
self.ref = documentRef
}
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let subscription = FirestoreSubscription(documentRef: ref, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
import Foundation
import FirebaseFirestore
import Combine
class FirestoreSubscription<S: Subscriber, Model: Codable>: Subscription {
private var subscriber: S?
private var listener: ListenerRegistration? = nil
init(subscriber: S) {
self.subscriber = subscriber
self.listener = nil
}
deinit {
listener?.remove()
}
func request(_ demand: Subscribers.Demand) {
}
func cancel() {
subscriber = nil
listener?.remove()
listener = nil
}
}
extension FirestoreSubscription where S.Input == [Document<Model>], S.Failure == Error {
convenience init(query: Query, subscriber: S) {
self.init(subscriber: subscriber)
self.listener = query.addSnapshotListener(result())
}
func result() -> FIRQuerySnapshotBlock {
{ [weak self] (snapshot, error) in
if let error = error {
self?.subscriber?.receive(completion: Subscribers.Completion.failure(error))
} else {
do {
let data = try snapshot!.documents.map { document -> Document<Model> in
let data = try document.data(as: Model.self, decoder: Firestore.Decoder())!
return .init(ref: document.reference, data: data)
}
_ = self?.subscriber?.receive(data)
} catch {
self?.subscriber?.receive(completion: Subscribers.Completion.failure(error))
}
}
}
}
}
extension FirestoreSubscription where S.Input == Document<Model>, S.Failure == Error {
convenience init(documentRef: DocumentReference, subscriber: S) {
self.init(subscriber: subscriber)
self.listener = documentRef.addSnapshotListener(result(ref: documentRef))
}
func result(ref: DocumentReference) -> FIRDocumentSnapshotBlock {
{ [weak self] (snapshot, error) in
if let error = error {
self?.subscriber?.receive(completion: Subscribers.Completion.failure(error))
} else {
do {
let data = try snapshot!.data(as: Model.self, decoder: Firestore.Decoder())!
_ = self?.subscriber?.receive(.init(ref: ref, data: data))
} catch {
self?.subscriber?.receive(completion: Subscribers.Completion.failure(error))
}
}
}
}
}
class FriendsViewModel: ObservableObject {
@Published var friends: [Document<User>] = []
var cancellable: AnyCancellable? = nil
@ObservedObject var authStore = AuthStore(auth: Auth.auth())
init() {
bind()
}
func bind() {
cancellable = Document<Room>.listen(query: Firestore.firestore().collection("user").whereField("friends", arrayContains: Auth.auth().currentUser!.uid).limit(to: 10)).sink(receiveCompletion: { error in
}, receiveValue: { [weak self] friends in
print("My friends..",friends.joined(separator: ","))
self?.friends = friends
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment