[firestore][ios] Add document onSnapshot functionality

This commit is contained in:
Chris Bianca 2017-10-02 15:45:07 +01:00
parent cda1c27b5c
commit d40f464f1c
5 changed files with 144 additions and 9 deletions

View File

@ -16,6 +16,10 @@ static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed";
static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed";
static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved";
// Firestore
static NSString *const FIRESTORE_COLLECTION_SYNC_EVENT = @"firestore_collection_sync_event";
static NSString *const FIRESTORE_DOCUMENT_SYNC_EVENT = @"firestore_document_sync_event";
// Storage
static NSString *const STORAGE_EVENT = @"storage_event";
static NSString *const STORAGE_ERROR = @"storage_error";

View File

@ -10,10 +10,13 @@
#import <React/RCTEventEmitter.h>
@interface RNFirebaseFirestore : RCTEventEmitter <RCTBridgeModule> {}
@property NSMutableDictionary *collectionReferences;
@property NSMutableDictionary *documentReferences;
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error;
+ (FIRFirestore *)getFirestoreForApp:(NSString *)appName;
+ (NSDictionary *)getJSError:(NSError *)nativeError;
@end

View File

@ -13,7 +13,7 @@ RCT_EXPORT_MODULE();
- (id)init {
self = [super init];
if (self != nil) {
_documentReferences = [[NSMutableDictionary alloc] init];
}
return self;
}
@ -109,6 +109,24 @@ RCT_EXPORT_METHOD(documentGetAll:(NSString *) appName
// Not supported on iOS out of the box
}
RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appName
path:(NSString *) path
listenerId:(nonnull NSNumber *) listenerId) {
RNFirebaseFirestoreDocumentReference *ref = [self getCachedDocumentForAppPath:appName path:path];
[ref offSnapshot:listenerId];
if (![ref hasListeners]) {
[self clearCachedDocumentForAppPath:appName path:path];
}
}
RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName
path:(NSString *) path
listenerId:(nonnull NSNumber *) listenerId) {
RNFirebaseFirestoreDocumentReference *ref = [self getCachedDocumentForAppPath:appName path:path];
[ref onSnapshot:listenerId];
}
RCT_EXPORT_METHOD(documentSet:(NSString *) appName
path:(NSString *) path
data:(NSDictionary *) data
@ -130,10 +148,8 @@ RCT_EXPORT_METHOD(documentUpdate:(NSString *) appName
* INTERNALS/UTILS
*/
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error {
// TODO
// NSDictionary *jsError = [RNFirebaseDatabase getJSError:databaseError];
// reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], databaseError);
reject(@"TODO", [error description], error);
NSDictionary *jsError = [RNFirebaseFirestore getJSError:error];
reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], error);
}
+ (FIRFirestore *)getFirestoreForApp:(NSString *)appName {
@ -145,12 +161,60 @@ RCT_EXPORT_METHOD(documentUpdate:(NSString *) appName
return [[RNFirebaseFirestoreCollectionReference alloc] initWithPathAndModifiers:appName path:path filters:filters orders:orders options:options];
}
- (RNFirebaseFirestoreDocumentReference *)getCachedDocumentForAppPath:(NSString *)appName path:(NSString *)path {
NSString *key = [NSString stringWithFormat:@"%@/%@", appName, path];
RNFirebaseFirestoreDocumentReference *ref = _documentReferences[key];
if (ref == nil) {
ref = [self getDocumentForAppPath:appName path:path];
_documentReferences[key] = ref;
}
return ref;
}
- (void)clearCachedDocumentForAppPath:(NSString *)appName path:(NSString *)path {
NSString *key = [NSString stringWithFormat:@"%@/%@", appName, path];
[_documentReferences removeObjectForKey:key];
}
- (RNFirebaseFirestoreDocumentReference *)getDocumentForAppPath:(NSString *)appName path:(NSString *)path {
return [[RNFirebaseFirestoreDocumentReference alloc] initWithPath:appName path:path];
return [[RNFirebaseFirestoreDocumentReference alloc] initWithPath:self app:appName path:path];
}
// TODO: Move to error util for use in other modules
+ (NSString *)getMessageWithService:(NSString *)message service:(NSString *)service fullCode:(NSString *)fullCode {
return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]];
}
+ (NSString *)getCodeWithService:(NSString *)service code:(NSString *)code {
return [NSString stringWithFormat:@"%@/%@", [service lowercaseString], [code lowercaseString]];
}
+ (NSDictionary *)getJSError:(NSError *)nativeError {
NSMutableDictionary *errorMap = [[NSMutableDictionary alloc] init];
[errorMap setValue:@(nativeError.code) forKey:@"nativeErrorCode"];
[errorMap setValue:[nativeError localizedDescription] forKey:@"nativeErrorMessage"];
NSString *code;
NSString *message;
NSString *service = @"Firestore";
// TODO: Proper error codes
switch (nativeError.code) {
default:
code = [RNFirebaseFirestore getCodeWithService:service code:@"unknown"];
message = [RNFirebaseFirestore getMessageWithService:@"An unknown error occurred." service:service fullCode:code];
break;
}
[errorMap setValue:code forKey:@"code"];
[errorMap setValue:message forKey:@"message"];
return errorMap;
}
- (NSArray<NSString *> *)supportedEvents {
return @[DATABASE_SYNC_EVENT, DATABASE_TRANSACTION_EVENT];
return @[FIRESTORE_COLLECTION_SYNC_EVENT, FIRESTORE_DOCUMENT_SYNC_EVENT];
}
@end

View File

@ -6,20 +6,27 @@
#if __has_include(<Firestore/FIRFirestore.h>)
#import <Firestore/Firestore.h>
#import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
@interface RNFirebaseFirestoreDocumentReference : NSObject
@property RCTEventEmitter *emitter;
@property NSString *app;
@property NSString *path;
@property FIRDocumentReference *ref;
@property NSMutableDictionary *listeners;
- (id)initWithPath:(NSString *)app path:(NSString *)path;
- (id)initWithPath:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path;
- (void)collections:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)create:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)delete:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)offSnapshot:(NSNumber *)listenerId;
- (void)onSnapshot:(NSNumber *)listenerId;
- (void)set:(NSDictionary *)data options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (BOOL)hasListeners;
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot;
@end

View File

@ -4,13 +4,16 @@
#if __has_include(<Firestore/FIRFirestore.h>)
- (id)initWithPath:(NSString *) app
- (id)initWithPath:(RCTEventEmitter *)emitter
app:(NSString *) app
path:(NSString *) path {
self = [super init];
if (self) {
_emitter = emitter;
_app = app;
_path = path;
_ref = [[RNFirebaseFirestore getFirestoreForApp:_app] documentWithPath:_path];
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
@ -46,6 +49,34 @@
}];
}
- (void)offSnapshot:(NSNumber *) listenerId {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
}
- (void)onSnapshot:(NSNumber *) listenerId {
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];
}
};
id<FIRListenerRegistration> listener = [_ref addSnapshotListener:listenerBlock];
_listeners[listenerId] = listener;
}
}
- (void)set:(NSDictionary *) data
options:(NSDictionary *) options
resolver:(RCTPromiseResolveBlock) resolve
@ -69,6 +100,10 @@
}];
}
- (BOOL)hasListeners {
return [[_listeners allKeys] count] > 0;
}
+ (void)handleWriteResponse:(NSError *) error
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
@ -95,6 +130,28 @@
return snapshot;
}
- (void)handleDocumentSnapshotError:(NSNumber *)listenerId
error:(NSError *)error {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
- (void)handleDocumentSnapshotEvent:(NSNumber *)listenerId
documentSnapshot:(FIRDocumentSnapshot *)documentSnapshot {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:@"document"];
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
#endif
@end