Files
react-native-firebase/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m
Andrzej Lewandowski 44c7c92acf [Android][IOS][Firestore] add arrayRemove and arrayUnion to FieldValue (#1624)
### Summary

Add methods arrayRemove and arrayUnion to work with arrays in firestore.

Fixes #1389

### Checklist
* [x]  Supports `Android`
* [x]  Supports `iOS`
* [x]  `e2e` tests added or updated in [/tests/e2e/*](/tests/e2e)
* [x]  Updated the documentation in the [docs repo](https://github.com/invertase/react-native-firebase-docs)

https://github.com/invertase/react-native-firebase-docs/pull/134

* [x]  Flow types updated
* [x]  Typescript types updated
2018-10-23 13:40:11 +01:00

461 lines
15 KiB
Objective-C

#import "RNFirebaseFirestoreDocumentReference.h"
@implementation RNFirebaseFirestoreDocumentReference
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
static NSMutableDictionary *_listeners;
static NSString *const typeKey = @"type";
static NSString *const keyPath = @"path";
static NSString *const keyData = @"data";
static NSString *const keyError = @"error";
static NSString *const valueKey = @"value";
static NSString *const keyMerge = @"merge";
static NSString *const keyAppName = @"appName";
static NSString *const keyLatitude = @"latitude";
static NSString *const keyMetadata = @"metadata";
static NSString *const keyLongitude = @"longitude";
static NSString *const keyFromCache = @"fromCache";
static NSString *const keyListenerId = @"listenerId";
static NSString *const keyDocumentSnapshot = @"documentSnapshot";
static NSString *const keyHasPendingWrites = @"hasPendingWrites";
static NSString *const keyIncludeMetaChanges = @"includeMetadataChanges";
static NSString *const typeNaN = @"nan";
static NSString *const typeNull = @"null";
static NSString *const typeBlob = @"blob";
static NSString *const typeDate = @"date";
static NSString *const typeArray = @"array";
static NSString *const typeObject = @"object";
static NSString *const typeString = @"string";
static NSString *const typeNumber = @"number";
static NSString *const typeDelete = @"delete";
static NSString *const typeBoolean = @"boolean";
static NSString *const typeInfinity = @"infinity";
static NSString *const typeGeoPoint = @"geopoint";
static NSString *const typeTimestamp = @"timestamp";
static NSString *const typeReference = @"reference";
static NSString *const typeDocumentId = @"documentid";
static NSString *const typeFieldValue = @"fieldvalue";
static NSString *const typeFieldValueUnion = @"union";
static NSString *const typeFieldValueRemove = @"remove";
static NSString *const typeFieldValueType = @"type";
static NSString *const typeFieldValueElements = @"elements";
- (id)initWithPath:(RCTEventEmitter *)emitter
appDisplayName:(NSString *)appDisplayName
path:(NSString *)path {
self = [super init];
if (self) {
_emitter = emitter;
_appDisplayName = appDisplayName;
_path = path;
_ref = [[RNFirebaseFirestore getFirestoreForApp:_appDisplayName] documentWithPath:_path];
}
// Initialise the static listeners object if required
if (!_listeners) {
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)delete:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
[_ref deleteDocumentWithCompletion:^(NSError *_Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
- (void)get:(NSDictionary *)getOptions
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
FIRFirestoreSource source;
if (getOptions && getOptions[@"source"]) {
if ([getOptions[@"source"] isEqualToString:@"server"]) {
source = FIRFirestoreSourceServer;
} else if ([getOptions[@"source"] isEqualToString:@"cache"]) {
source = FIRFirestoreSourceCache;
} else {
source = FIRFirestoreSourceDefault;
}
} else {
source = FIRFirestoreSourceDefault;
}
[_ref getDocumentWithSource:source completion:^(FIRDocumentSnapshot *_Nullable snapshot, NSError *_Nullable error) {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
NSDictionary *data = [RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot];
resolve(data);
}
}];
}
+ (void)offSnapshot:(NSString *)listenerId {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
}
- (void)onSnapshot:(NSString *)listenerId
docListenOptions:(NSDictionary *)docListenOptions {
if (_listeners[listenerId] == nil) {
id listenerBlock = ^(FIRDocumentSnapshot *_Nullable snapshot, NSError *_Nullable error) {
if (error) {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
[self handleDocumentSnapshotError:listenerId error:error];
} else {
[self handleDocumentSnapshotEvent:listenerId documentSnapshot:snapshot];
}
};
bool includeMetadataChanges;
if (docListenOptions && docListenOptions[keyIncludeMetaChanges]) {
includeMetadataChanges = true;
} else {
includeMetadataChanges = false;
}
id<FIRListenerRegistration>
listener = [_ref addSnapshotListenerWithIncludeMetadataChanges:includeMetadataChanges listener:listenerBlock];
_listeners[listenerId] = listener;
}
}
- (void)set:(NSDictionary *)data
options:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
NSDictionary *dictionary =
[RNFirebaseFirestoreDocumentReference parseJSMap:[RNFirebaseFirestore getFirestoreForApp:_appDisplayName] jsMap:data];
if (options && options[keyMerge]) {
[_ref setData:dictionary merge:true completion:^(NSError *_Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
} else {
[_ref setData:dictionary completion:^(NSError *_Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
}
- (void)update:(NSDictionary *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
NSDictionary *dictionary =
[RNFirebaseFirestoreDocumentReference parseJSMap:[RNFirebaseFirestore getFirestoreForApp:_appDisplayName] jsMap:data];
[_ref updateData:dictionary completion:^(NSError *_Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
+ (void)handleWriteResponse:(NSError *)error
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
resolve(nil);
}
}
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot {
NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
[snapshot setValue:documentSnapshot.reference.path forKey:keyPath];
if (documentSnapshot.exists) {
[snapshot setValue:[RNFirebaseFirestoreDocumentReference buildNativeMap:documentSnapshot.data] forKey:keyData];
}
if (documentSnapshot.metadata) {
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init];
[metadata setValue:@(documentSnapshot.metadata.fromCache) forKey:keyFromCache];
[metadata setValue:@(documentSnapshot.metadata.hasPendingWrites) forKey:keyHasPendingWrites];
[snapshot setValue:metadata forKey:keyMetadata];
}
return snapshot;
}
- (void)handleDocumentSnapshotError:(NSString *)listenerId
error:(NSError *)error {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_path forKey:keyPath];
[event setValue:listenerId forKey:keyListenerId];
[event setValue:_appDisplayName forKey:keyAppName];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:keyError];
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
- (void)handleDocumentSnapshotEvent:(NSString *)listenerId
documentSnapshot:(FIRDocumentSnapshot *)documentSnapshot {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_path forKey:keyPath];
[event setValue:listenerId forKey:keyListenerId];
[event setValue:_appDisplayName forKey:keyAppName];
[event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:keyDocumentSnapshot];
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
+ (NSDictionary *)buildNativeMap:(NSDictionary *)nativeMap {
NSMutableDictionary *map = [[NSMutableDictionary alloc] init];
[nativeMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
NSDictionary *typeMap = [RNFirebaseFirestoreDocumentReference buildTypeMap:obj];
map[key] = typeMap;
}];
return map;
}
+ (NSArray *)buildNativeArray:(NSArray *)nativeArray {
NSMutableArray *array = [[NSMutableArray alloc] init];
[nativeArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSDictionary *typeMap = [RNFirebaseFirestoreDocumentReference buildTypeMap:obj];
[array addObject:typeMap];
}];
return array;
}
/**
*
* @param value
* @return
*/
+ (NSDictionary *)buildTypeMap:(id)value {
NSMutableDictionary *typeMap = [[NSMutableDictionary alloc] init];
// null
if (value == nil) {
typeMap[typeKey] = typeNull;
return typeMap;
}
// strings
if ([value isKindOfClass:[NSString class]]) {
typeMap[typeKey] = typeString;
typeMap[valueKey] = value;
return typeMap;
}
// objects
if ([value isKindOfClass:[NSDictionary class]]) {
typeMap[typeKey] = typeObject;
typeMap[valueKey] = [RNFirebaseFirestoreDocumentReference buildNativeMap:value];
return typeMap;
}
// array
if ([value isKindOfClass:[NSArray class]]) {
typeMap[typeKey] = typeArray;
typeMap[valueKey] = [RNFirebaseFirestoreDocumentReference buildNativeArray:value];
return typeMap;
}
// reference
if ([value isKindOfClass:[FIRDocumentReference class]]) {
typeMap[typeKey] = typeReference;
FIRDocumentReference *ref = (FIRDocumentReference *) value;
typeMap[valueKey] = [ref path];
return typeMap;
}
// geopoint
if ([value isKindOfClass:[FIRGeoPoint class]]) {
typeMap[typeKey] = typeGeoPoint;
FIRGeoPoint *point = (FIRGeoPoint *) value;
NSMutableDictionary *geopoint = [[NSMutableDictionary alloc] init];
geopoint[keyLatitude] = @([point latitude]);
geopoint[keyLongitude] = @([point longitude]);
typeMap[valueKey] = geopoint;
return typeMap;
}
// date
if ([value isKindOfClass:[NSDate class]]) {
typeMap[typeKey] = typeDate;
// round is required otherwise iOS ends up with .999 and loses a millisecond
// when going between native and JS
typeMap[valueKey] = @(round([(NSDate *) value timeIntervalSince1970] * 1000.0));
return typeMap;
}
// number / boolean / infinity / nan
if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *number = (NSNumber *) value;
// infinity
if (number == @(INFINITY)) {
typeMap[typeKey] = typeInfinity;
return typeMap;
}
// boolean
if (number == [NSValue valueWithPointer:(void *) kCFBooleanFalse]
|| number == [NSValue valueWithPointer:(void *) kCFBooleanTrue]) {
typeMap[typeKey] = typeBoolean;
typeMap[valueKey] = value;
return typeMap;
}
// nan
if ([[value description].lowercaseString isEqual:@"nan"]) {
typeMap[typeKey] = typeNaN;
return typeMap;
}
// number
typeMap[typeKey] = typeNumber;
typeMap[valueKey] = value;
return typeMap;
}
// blobs (converted to base64)
if ([value isKindOfClass:[NSData class]]) {
NSData *blob = (NSData *) value;
typeMap[typeKey] = typeBlob;
typeMap[valueKey] = [blob base64EncodedStringWithOptions:0];
return typeMap;
}
DLog(@"RNFirebaseFirestore: Unsupported value sent to buildTypeMap - class type is %@",
NSStringFromClass([value class]));
typeMap[typeKey] = typeNull;
return typeMap;
}
/**
*
* @param firestore
* @param jsMap
* @return
*/
+ (NSDictionary *)parseJSMap:(FIRFirestore *)firestore
jsMap:(NSDictionary *)jsMap {
NSMutableDictionary *map = [[NSMutableDictionary alloc] init];
if (jsMap) {
[jsMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
map[key] = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:obj];
}];
}
return map;
}
/**
*
* @param firestore
* @param jsArray
* @return
*/
+ (NSArray *)parseJSArray:(FIRFirestore *)firestore
jsArray:(NSArray *)jsArray {
NSMutableArray *array = [[NSMutableArray alloc] init];
if (jsArray) {
[jsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
[array addObject:[RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:obj]];
}];
}
return array;
}
/**
*
* @param firestore
* @param jsTypeMap
* @return
*/
+ (id)parseJSTypeMap:(FIRFirestore *)firestore
jsTypeMap:(NSDictionary *)jsTypeMap {
id value = jsTypeMap[valueKey];
NSString *type = jsTypeMap[typeKey];
if ([type isEqualToString:typeArray]) {
return [RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:value];
}
if ([type isEqualToString:typeObject]) {
return [RNFirebaseFirestoreDocumentReference parseJSMap:firestore jsMap:value];
}
if ([type isEqualToString:typeReference]) {
return [firestore documentWithPath:value];
}
if ([type isEqualToString:typeBlob]) {
return [[NSData alloc] initWithBase64EncodedString:(NSString *) value options:0];
}
if ([type isEqualToString:typeGeoPoint]) {
NSDictionary *geopoint = (NSDictionary *) value;
NSNumber *latitude = geopoint[keyLatitude];
NSNumber *longitude = geopoint[keyLongitude];
return [[FIRGeoPoint alloc] initWithLatitude:[latitude doubleValue] longitude:[longitude doubleValue]];
}
if ([type isEqualToString:typeDate]) {
return [NSDate dateWithTimeIntervalSince1970:([(NSNumber *) value doubleValue] / 1000.0)];
}
if ([type isEqualToString:typeDocumentId]) {
return [FIRFieldPath documentID];
}
if ([type isEqualToString:typeFieldValue]) {
NSDictionary *fieldValueMap = (NSDictionary *) value;
NSString *string = (NSString *) fieldValueMap[typeFieldValueType];
if ([string isEqualToString:typeDelete]) {
return [FIRFieldValue fieldValueForDelete];
}
if ([string isEqualToString:typeTimestamp]) {
return [FIRFieldValue fieldValueForServerTimestamp];
}
if ([string isEqualToString:typeFieldValueUnion]) {
NSDictionary *elements = (NSDictionary *) value[typeFieldValueElements];
return [FIRFieldValue fieldValueForArrayUnion:elements];
}
if ([string isEqualToString:typeFieldValueRemove]) {
NSDictionary *elements = (NSDictionary *) value[typeFieldValueElements];
return [FIRFieldValue fieldValueForArrayRemove:elements];
}
DLog(@"RNFirebaseFirestore: Unsupported field-value sent to parseJSTypeMap - value is %@",
NSStringFromClass([value class]));
return nil;
}
if ([type isEqualToString:typeInfinity]) {
return @(INFINITY);
}
if ([type isEqualToString:typeNaN]) {
return [NSDecimalNumber notANumber];
}
if ([type isEqualToString:typeBoolean] || [type isEqualToString:typeNumber] || [type isEqualToString:typeString]
|| [type isEqualToString:typeNull]) {
return value;
}
return nil;
}
#endif
@end