| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- /*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import SwiftUI
- import FirebaseFirestore
- @available(iOS 14.0, macOS 11.0, macCatalyst 14.0, tvOS 14.0, watchOS 7.0, *)
- internal class FirestoreQueryObservable<T>: ObservableObject {
- @Published var items: T
- private let firestore = Firestore.firestore()
- private var listener: ListenerRegistration?
- private var setupListener: (() -> Void)!
- internal var shouldUpdateListener = true
- internal var configuration: FirestoreQuery<T>.Configuration {
- didSet {
- // prevent never-ending update cycle when updating the error field
- guard shouldUpdateListener else { return }
- removeListener()
- setupListener()
- }
- }
- init<U: Decodable>(configuration: FirestoreQuery<T>.Configuration) where T == [U] {
- items = []
- self.configuration = configuration
- setupListener = createListener { [weak self] querySnapshot, error in
- if let error = error {
- self?.items = []
- self?.projectError(error)
- return
- } else {
- self?.projectError(nil)
- }
- guard let documents = querySnapshot?.documents else {
- self?.items = []
- return
- }
- let decodedDocuments: [U] = documents.compactMap { queryDocumentSnapshot in
- let result = Result { try queryDocumentSnapshot.data(as: U.self) }
- switch result {
- case let .success(decodedDocument):
- return decodedDocument
- case let .failure(error):
- self?.projectError(error)
- return nil
- }
- }
- if self?.configuration.error != nil {
- if configuration.decodingFailureStrategy == .raise {
- self?.items = []
- } else {
- self?.items = decodedDocuments
- }
- } else {
- self?.items = decodedDocuments
- }
- }
- setupListener()
- }
- init<U: Decodable>(configuration: FirestoreQuery<T>.Configuration) where T == Result<[U], Error> {
- items = .success([])
- self.configuration = configuration
- setupListener = createListener { [weak self] querySnapshot, error in
- if let error = error {
- self?.items = .failure(error)
- self?.projectError(error)
- return
- } else {
- self?.projectError(nil)
- }
- guard let documents = querySnapshot?.documents else {
- self?.items = .success([])
- return
- }
- let decodedDocuments: [U] = documents.compactMap { queryDocumentSnapshot in
- let result = Result { try queryDocumentSnapshot.data(as: U.self) }
- switch result {
- case let .success(decodedDocument):
- return decodedDocument
- case let .failure(error):
- self?.projectError(error)
- return nil
- }
- }
- if let error = self?.configuration.error {
- if configuration.decodingFailureStrategy == .raise {
- self?.items = .failure(error)
- } else {
- self?.items = .success(decodedDocuments)
- }
- } else {
- self?.items = .success(decodedDocuments)
- }
- }
- setupListener()
- }
- deinit {
- removeListener()
- }
- private func createListener(with handler: @escaping (QuerySnapshot?, Error?) -> Void)
- -> () -> Void {
- return {
- var query: Query = self.firestore.collection(self.configuration.path)
- for predicate in self.configuration.predicates {
- switch predicate {
- case let .isEqualTo(field, value):
- query = query.whereField(field, isEqualTo: value)
- case let .isIn(field, values):
- query = query.whereField(field, in: values)
- case let .isNotIn(field, values):
- query = query.whereField(field, notIn: values)
- case let .arrayContains(field, value):
- query = query.whereField(field, arrayContains: value)
- case let .arrayContainsAny(field, values):
- query = query.whereField(field, arrayContainsAny: values)
- case let .isLessThan(field, value):
- query = query.whereField(field, isLessThan: value)
- case let .isGreaterThan(field, value):
- query = query.whereField(field, isGreaterThan: value)
- case let .isLessThanOrEqualTo(field, value):
- query = query.whereField(field, isLessThanOrEqualTo: value)
- case let .isGreaterThanOrEqualTo(field, value):
- query = query.whereField(field, isGreaterThanOrEqualTo: value)
- case let .orderBy(field, value):
- query = query.order(by: field, descending: value)
- case let .limitTo(field):
- query = query.limit(to: field)
- case let .limitToLast(field):
- query = query.limit(toLast: field)
- }
- }
- self.listener = query.addSnapshotListener(handler)
- }
- }
- private func projectError(_ error: Error?) {
- shouldUpdateListener = false
- configuration.error = error
- shouldUpdateListener = true
- }
- private func removeListener() {
- listener?.remove()
- listener = nil
- }
- }
|