#import "RNFirebaseFirestoreCollectionReference.h" @implementation RNFirebaseFirestoreCollectionReference #if __has_include() static NSMutableDictionary *_listeners; - (id)initWithPathAndModifiers:(RCTEventEmitter *) emitter appDisplayName:(NSString *) appDisplayName path:(NSString *) path filters:(NSArray *) filters orders:(NSArray *) orders options:(NSDictionary *) options { self = [super init]; if (self) { _emitter = emitter; _appDisplayName = appDisplayName; _path = path; _filters = filters; _orders = orders; _options = options; _query = [self buildQuery]; } // Initialise the static listeners object if required if (!_listeners) { _listeners = [[NSMutableDictionary alloc] init]; } return self; } - (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject { [_query getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { [RNFirebaseFirestore promiseRejectException:reject error:error]; } else { NSDictionary *data = [RNFirebaseFirestoreCollectionReference snapshotToDictionary:snapshot]; resolve(data); } }]; } + (void)offSnapshot:(NSString *) listenerId { id listener = _listeners[listenerId]; if (listener) { [_listeners removeObjectForKey:listenerId]; [listener remove]; } } - (void)onSnapshot:(NSString *) listenerId queryListenOptions:(NSDictionary *) queryListenOptions { if (_listeners[listenerId] == nil) { id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { id listener = _listeners[listenerId]; if (listener) { [_listeners removeObjectForKey:listenerId]; [listener remove]; } [self handleQuerySnapshotError:listenerId error:error]; } else { [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; } }; FIRQueryListenOptions *options = [[FIRQueryListenOptions alloc] init]; if (queryListenOptions) { if (queryListenOptions[@"includeDocumentMetadataChanges"]) { [options includeDocumentMetadataChanges:TRUE]; } if (queryListenOptions[@"includeQueryMetadataChanges"]) { [options includeQueryMetadataChanges:TRUE]; } } id listener = [_query addSnapshotListenerWithOptions:options listener:listenerBlock]; _listeners[listenerId] = listener; } } - (FIRQuery *)buildQuery { FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:_appDisplayName]; FIRQuery *query = (FIRQuery*)[firestore collectionWithPath:_path]; query = [self applyFilters:firestore query:query]; query = [self applyOrders:query]; query = [self applyOptions:firestore query:query]; return query; } - (FIRQuery *)applyFilters:(FIRFirestore *) firestore query:(FIRQuery *) query { for (NSDictionary *filter in _filters) { NSDictionary *fieldPathDictionary = filter[@"fieldPath"]; NSString *fieldPathType = fieldPathDictionary[@"type"]; NSString *operator = filter[@"operator"]; NSDictionary *jsValue = filter[@"value"]; id value = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:jsValue]; if ([fieldPathType isEqualToString:@"string"]) { NSString *fieldPath = fieldPathDictionary[@"string"]; if ([operator isEqualToString:@"EQUAL"]) { query = [query queryWhereField:fieldPath isEqualTo:value]; } else if ([operator isEqualToString:@"GREATER_THAN"]) { query = [query queryWhereField:fieldPath isGreaterThan:value]; } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { query = [query queryWhereField:fieldPath isGreaterThanOrEqualTo:value]; } else if ([operator isEqualToString:@"LESS_THAN"]) { query = [query queryWhereField:fieldPath isLessThan:value]; } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { query = [query queryWhereField:fieldPath isLessThanOrEqualTo:value]; } } else { NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; if ([operator isEqualToString:@"EQUAL"]) { query = [query queryWhereFieldPath:fieldPath isEqualTo:value]; } else if ([operator isEqualToString:@"GREATER_THAN"]) { query = [query queryWhereFieldPath:fieldPath isGreaterThan:value]; } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { query = [query queryWhereFieldPath:fieldPath isGreaterThanOrEqualTo:value]; } else if ([operator isEqualToString:@"LESS_THAN"]) { query = [query queryWhereFieldPath:fieldPath isLessThan:value]; } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { query = [query queryWhereFieldPath:fieldPath isLessThanOrEqualTo:value]; } } } return query; } - (FIRQuery *)applyOrders:(FIRQuery *) query { for (NSDictionary *order in _orders) { NSString *direction = order[@"direction"]; NSDictionary *fieldPathDictionary = order[@"fieldPath"]; NSString *fieldPathType = fieldPathDictionary[@"type"]; if ([fieldPathType isEqualToString:@"string"]) { NSString *fieldPath = fieldPathDictionary[@"string"]; query = [query queryOrderedByField:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; } else { NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; query = [query queryOrderedByFieldPath:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; } } return query; } - (FIRQuery *)applyOptions:(FIRFirestore *) firestore query:(FIRQuery *) query { if (_options[@"endAt"]) { query = [query queryEndingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endAt"]]]; } if (_options[@"endBefore"]) { query = [query queryEndingBeforeValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endBefore"]]]; } if (_options[@"limit"]) { query = [query queryLimitedTo:[_options[@"limit"] intValue]]; } if (_options[@"offset"]) { // iOS doesn't support offset } if (_options[@"selectFields"]) { // iOS doesn't support selectFields } if (_options[@"startAfter"]) { query = [query queryStartingAfterValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAfter"]]]; } if (_options[@"startAt"]) { query = [query queryStartingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAt"]]]; } return query; } - (void)handleQuerySnapshotError:(NSString *)listenerId error:(NSError *)error { NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; [event setValue:_appDisplayName forKey:@"appName"]; [event setValue:_path forKey:@"path"]; [event setValue:listenerId forKey:@"listenerId"]; [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; } - (void)handleQuerySnapshotEvent:(NSString *)listenerId querySnapshot:(FIRQuerySnapshot *)querySnapshot { NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; [event setValue:_appDisplayName forKey:@"appName"]; [event setValue:_path forKey:@"path"]; [event setValue:listenerId forKey:@"listenerId"]; [event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"]; [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; } + (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot { NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; [snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"]; [snapshot setValue:[self documentSnapshotsToArray:querySnapshot.documents] forKey:@"documents"]; if (querySnapshot.metadata) { NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init]; [metadata setValue:@(querySnapshot.metadata.fromCache) forKey:@"fromCache"]; [metadata setValue:@(querySnapshot.metadata.hasPendingWrites) forKey:@"hasPendingWrites"]; [snapshot setValue:metadata forKey:@"metadata"]; } return snapshot; } + (NSArray *)documentChangesToArray:(NSArray *) documentChanges { NSMutableArray *changes = [[NSMutableArray alloc] init]; for (FIRDocumentChange *change in documentChanges) { [changes addObject:[self documentChangeToDictionary:change]]; } return changes; } + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange { NSMutableDictionary *change = [[NSMutableDictionary alloc] init]; [change setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentChange.document] forKey:@"document"]; [change setValue:@(documentChange.newIndex) forKey:@"newIndex"]; [change setValue:@(documentChange.oldIndex) forKey:@"oldIndex"]; if (documentChange.type == FIRDocumentChangeTypeAdded) { [change setValue:@"added" forKey:@"type"]; } else if (documentChange.type == FIRDocumentChangeTypeRemoved) { [change setValue:@"removed" forKey:@"type"]; } else if (documentChange.type == FIRDocumentChangeTypeModified) { [change setValue:@"modified" forKey:@"type"]; } return change; } + (NSArray *)documentSnapshotsToArray:(NSArray *) documentSnapshots { NSMutableArray *snapshots = [[NSMutableArray alloc] init]; for (FIRDocumentSnapshot *snapshot in documentSnapshots) { [snapshots addObject:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]]; } return snapshots; } #endif @end