[firestore] Add collection `onSnapshot` support
This commit is contained in:
parent
d40f464f1c
commit
22f7d77f54
|
@ -32,8 +32,6 @@ import io.invertase.firebase.Utils;
|
|||
|
||||
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseFirestore";
|
||||
private HashMap<String, RNFirebaseFirestoreCollectionReference> collectionReferences = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseFirestoreDocumentReference> documentReferences = new HashMap<>();
|
||||
// private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
||||
RNFirebaseFirestore(ReactApplicationContext reactContext) {
|
||||
|
@ -51,6 +49,20 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
ref.get(promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void collectionOffSnapshot(String appName, String path, ReadableArray filters,
|
||||
ReadableArray orders, ReadableMap options, String listenerId) {
|
||||
RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void collectionOnSnapshot(String appName, String path, ReadableArray filters,
|
||||
ReadableArray orders, ReadableMap options, String listenerId) {
|
||||
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options);
|
||||
ref.onSnapshot(listenerId);
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void documentBatch(final String appName, final ReadableArray writes,
|
||||
final ReadableMap commitOptions, final Promise promise) {
|
||||
|
@ -134,18 +146,13 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void documentOffSnapshot(String appName, String path, int listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference ref = getCachedDocumentForAppPath(appName, path);
|
||||
ref.offSnapshot(listenerId);
|
||||
|
||||
if (!ref.hasListeners()) {
|
||||
clearCachedDocumentForAppPath(appName, path);
|
||||
}
|
||||
public void documentOffSnapshot(String appName, String path, String listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference.offSnapshot(listenerId);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void documentOnSnapshot(String appName, String path, int listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference ref = getCachedDocumentForAppPath(appName, path);
|
||||
public void documentOnSnapshot(String appName, String path, String listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
|
||||
ref.onSnapshot(listenerId);
|
||||
}
|
||||
|
||||
|
@ -204,36 +211,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
ReadableArray filters,
|
||||
ReadableArray orders,
|
||||
ReadableMap options) {
|
||||
return new RNFirebaseFirestoreCollectionReference(appName, path, filters, orders, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached document reference for a specific app and path
|
||||
*
|
||||
* @param appName
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
private RNFirebaseFirestoreDocumentReference getCachedDocumentForAppPath(String appName, String path) {
|
||||
String key = appName + "/" + path;
|
||||
RNFirebaseFirestoreDocumentReference ref = documentReferences.get(key);
|
||||
if (ref == null) {
|
||||
ref = getDocumentForAppPath(appName, path);
|
||||
documentReferences.put(key, ref);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a cached document reference for a specific app and path
|
||||
*
|
||||
* @param appName
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
private void clearCachedDocumentForAppPath(String appName, String path) {
|
||||
String key = appName + "/" + path;
|
||||
documentReferences.remove(key);
|
||||
return new RNFirebaseFirestoreCollectionReference(this.getReactApplicationContext(), appName, path, filters, orders, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,16 +4,21 @@ package io.invertase.firebase.firestore;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.firestore.EventListener;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreException;
|
||||
import com.google.firebase.firestore.ListenerRegistration;
|
||||
import com.google.firebase.firestore.Query;
|
||||
import com.google.firebase.firestore.QuerySnapshot;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -21,21 +26,26 @@ import io.invertase.firebase.Utils;
|
|||
|
||||
public class RNFirebaseFirestoreCollectionReference {
|
||||
private static final String TAG = "RNFSCollectionReference";
|
||||
private static Map<String, ListenerRegistration> collectionSnapshotListeners = new HashMap<>();
|
||||
|
||||
private final String appName;
|
||||
private final String path;
|
||||
private final ReadableArray filters;
|
||||
private final ReadableArray orders;
|
||||
private final ReadableMap options;
|
||||
private final Query query;
|
||||
private ReactContext reactContext;
|
||||
|
||||
RNFirebaseFirestoreCollectionReference(String appName, String path, ReadableArray filters,
|
||||
ReadableArray orders, ReadableMap options) {
|
||||
RNFirebaseFirestoreCollectionReference(ReactContext reactContext, String appName, String path,
|
||||
ReadableArray filters, ReadableArray orders,
|
||||
ReadableMap options) {
|
||||
this.appName = appName;
|
||||
this.path = path;
|
||||
this.filters = filters;
|
||||
this.orders = orders;
|
||||
this.options = options;
|
||||
this.query = buildQuery();
|
||||
this.reactContext = reactContext;
|
||||
}
|
||||
|
||||
void get(final Promise promise) {
|
||||
|
@ -54,6 +64,42 @@ public class RNFirebaseFirestoreCollectionReference {
|
|||
});
|
||||
}
|
||||
|
||||
public static void offSnapshot(final String listenerId) {
|
||||
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
|
||||
if (listenerRegistration != null) {
|
||||
listenerRegistration.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSnapshot(final String listenerId) {
|
||||
if (!collectionSnapshotListeners.containsKey(listenerId)) {
|
||||
final EventListener<QuerySnapshot> listener = new EventListener<QuerySnapshot>() {
|
||||
@Override
|
||||
public void onEvent(QuerySnapshot querySnapshot, FirebaseFirestoreException exception) {
|
||||
if (exception == null) {
|
||||
handleQuerySnapshotEvent(listenerId, querySnapshot);
|
||||
} else {
|
||||
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
|
||||
if (listenerRegistration != null) {
|
||||
listenerRegistration.remove();
|
||||
}
|
||||
handleQuerySnapshotError(listenerId, exception);
|
||||
}
|
||||
}
|
||||
};
|
||||
ListenerRegistration listenerRegistration = this.query.addSnapshotListener(listener);
|
||||
collectionSnapshotListeners.put(listenerId, listenerRegistration);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
boolean hasListeners() {
|
||||
return !collectionSnapshotListeners.isEmpty();
|
||||
}
|
||||
|
||||
private Query buildQuery() {
|
||||
Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path);
|
||||
query = applyFilters(query);
|
||||
|
@ -134,4 +180,39 @@ public class RNFirebaseFirestoreCollectionReference {
|
|||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles documentSnapshot events.
|
||||
*
|
||||
* @param listenerId
|
||||
* @param querySnapshot
|
||||
*/
|
||||
private void handleQuerySnapshotEvent(String listenerId, QuerySnapshot querySnapshot) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
WritableMap data = FirestoreSerialize.snapshotToWritableMap(querySnapshot);
|
||||
|
||||
event.putString("appName", appName);
|
||||
event.putString("path", path);
|
||||
event.putString("listenerId", listenerId);
|
||||
event.putMap("querySnapshot", data);
|
||||
|
||||
Utils.sendEvent(reactContext, "firestore_collection_sync_event", event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a documentSnapshot error event
|
||||
*
|
||||
* @param listenerId
|
||||
* @param exception
|
||||
*/
|
||||
private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
|
||||
event.putString("appName", appName);
|
||||
event.putString("path", path);
|
||||
event.putString("listenerId", listenerId);
|
||||
event.putMap("error", RNFirebaseFirestore.getJSError(exception));
|
||||
|
||||
Utils.sendEvent(reactContext, "firestore_collection_sync_event", event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@ import io.invertase.firebase.Utils;
|
|||
|
||||
public class RNFirebaseFirestoreDocumentReference {
|
||||
private static final String TAG = "RNFBFSDocumentReference";
|
||||
private static Map<String, ListenerRegistration> documentSnapshotListeners = new HashMap<>();
|
||||
|
||||
private final String appName;
|
||||
private final String path;
|
||||
private ReactContext reactContext;
|
||||
private final DocumentReference ref;
|
||||
private Map<Integer, ListenerRegistration> documentSnapshotListeners = new HashMap<>();
|
||||
|
||||
RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) {
|
||||
this.appName = appName;
|
||||
|
@ -79,14 +80,14 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
});
|
||||
}
|
||||
|
||||
public void offSnapshot(final int listenerId) {
|
||||
public static void offSnapshot(final String listenerId) {
|
||||
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
|
||||
if (listenerRegistration != null) {
|
||||
listenerRegistration.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSnapshot(final int listenerId) {
|
||||
public void onSnapshot(final String listenerId) {
|
||||
if (!documentSnapshotListeners.containsKey(listenerId)) {
|
||||
final EventListener<DocumentSnapshot> listener = new EventListener<DocumentSnapshot>() {
|
||||
@Override
|
||||
|
@ -154,7 +155,7 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
public boolean hasListeners() {
|
||||
boolean hasListeners() {
|
||||
return !documentSnapshotListeners.isEmpty();
|
||||
}
|
||||
|
||||
|
@ -164,14 +165,14 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
* @param listenerId
|
||||
* @param documentSnapshot
|
||||
*/
|
||||
private void handleDocumentSnapshotEvent(int listenerId, DocumentSnapshot documentSnapshot) {
|
||||
private void handleDocumentSnapshotEvent(String listenerId, DocumentSnapshot documentSnapshot) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
WritableMap data = FirestoreSerialize.snapshotToWritableMap(documentSnapshot);
|
||||
|
||||
event.putString("appName", appName);
|
||||
event.putString("path", path);
|
||||
event.putInt("listenerId", listenerId);
|
||||
event.putMap("document", data);
|
||||
event.putString("listenerId", listenerId);
|
||||
event.putMap("documentSnapshot", data);
|
||||
|
||||
Utils.sendEvent(reactContext, "firestore_document_sync_event", event);
|
||||
}
|
||||
|
@ -182,12 +183,12 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
* @param listenerId
|
||||
* @param exception
|
||||
*/
|
||||
private void handleDocumentSnapshotError(int listenerId, FirebaseFirestoreException exception) {
|
||||
private void handleDocumentSnapshotError(String listenerId, FirebaseFirestoreException exception) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
|
||||
event.putString("appName", appName);
|
||||
event.putString("path", path);
|
||||
event.putInt("listenerId", listenerId);
|
||||
event.putString("listenerId", listenerId);
|
||||
event.putMap("error", RNFirebaseFirestore.getJSError(exception));
|
||||
|
||||
Utils.sendEvent(reactContext, "firestore_document_sync_event", event);
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface RNFirebaseFirestore : RCTEventEmitter <RCTBridgeModule> {}
|
||||
@property NSMutableDictionary *collectionReferences;
|
||||
@property NSMutableDictionary *documentReferences;
|
||||
|
||||
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ RCT_EXPORT_MODULE();
|
|||
- (id)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_documentReferences = [[NSMutableDictionary alloc] init];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -28,6 +28,25 @@ RCT_EXPORT_METHOD(collectionGet:(NSString *) appName
|
|||
[[self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options] get:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(collectionOffSnapshot:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
[RNFirebaseFirestoreCollectionReference offSnapshot:listenerId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options];
|
||||
[ref onSnapshot:listenerId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentBatch:(NSString *) appName
|
||||
writes:(NSArray *) writes
|
||||
commitOptions:(NSDictionary *) commitOptions
|
||||
|
@ -111,19 +130,14 @@ RCT_EXPORT_METHOD(documentGetAll:(NSString *) appName
|
|||
|
||||
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];
|
||||
}
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
[RNFirebaseFirestoreDocumentReference offSnapshot:listenerId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
listenerId:(nonnull NSNumber *) listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference *ref = [self getCachedDocumentForAppPath:appName path:path];
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appName path:path];
|
||||
[ref onSnapshot:listenerId];
|
||||
}
|
||||
|
||||
|
@ -158,23 +172,7 @@ RCT_EXPORT_METHOD(documentUpdate:(NSString *) appName
|
|||
}
|
||||
|
||||
- (RNFirebaseFirestoreCollectionReference *)getCollectionForAppPath:(NSString *)appName path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options {
|
||||
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];
|
||||
return [[RNFirebaseFirestoreCollectionReference alloc] initWithPathAndModifiers:self app:appName path:path filters:filters orders:orders options:options];
|
||||
}
|
||||
|
||||
- (RNFirebaseFirestoreDocumentReference *)getDocumentForAppPath:(NSString *)appName path:(NSString *)path {
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
#if __has_include(<Firestore/FIRFirestore.h>)
|
||||
|
||||
#import <Firestore/Firestore.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import "RNFirebaseEvents.h"
|
||||
#import "RNFirebaseFirestore.h"
|
||||
#import "RNFirebaseFirestoreDocumentReference.h"
|
||||
|
||||
@interface RNFirebaseFirestoreCollectionReference : NSObject
|
||||
@property RCTEventEmitter *emitter;
|
||||
@property NSString *app;
|
||||
@property NSString *path;
|
||||
@property NSArray *filters;
|
||||
|
@ -16,8 +19,10 @@
|
|||
@property NSDictionary *options;
|
||||
@property FIRQuery *query;
|
||||
|
||||
- (id)initWithPathAndModifiers:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options;
|
||||
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options;
|
||||
- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
|
||||
+ (void)offSnapshot:(NSString *)listenerId;
|
||||
- (void)onSnapshot:(NSString *)listenerId;
|
||||
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot;
|
||||
@end
|
||||
|
||||
|
|
|
@ -4,13 +4,17 @@
|
|||
|
||||
#if __has_include(<Firestore/FIRFirestore.h>)
|
||||
|
||||
- (id)initWithPathAndModifiers:(NSString *) app
|
||||
static NSMutableDictionary *_listeners;
|
||||
|
||||
- (id)initWithPathAndModifiers:(RCTEventEmitter *) emitter
|
||||
app:(NSString *) app
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_emitter = emitter;
|
||||
_app = app;
|
||||
_path = path;
|
||||
_filters = filters;
|
||||
|
@ -18,6 +22,10 @@
|
|||
_options = options;
|
||||
_query = [self buildQuery];
|
||||
}
|
||||
// Initialise the static listeners object if required
|
||||
if (!_listeners) {
|
||||
_listeners = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -33,6 +41,33 @@
|
|||
}];
|
||||
}
|
||||
|
||||
+ (void)offSnapshot:(NSString *) listenerId {
|
||||
id<FIRListenerRegistration> listener = _listeners[listenerId];
|
||||
if (listener) {
|
||||
[_listeners removeObjectForKey:listenerId];
|
||||
[listener remove];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSnapshot:(NSString *) listenerId {
|
||||
if (_listeners[listenerId] == nil) {
|
||||
id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
id<FIRListenerRegistration> listener = _listeners[listenerId];
|
||||
if (listener) {
|
||||
[_listeners removeObjectForKey:listenerId];
|
||||
[listener remove];
|
||||
}
|
||||
[self handleQuerySnapshotError:listenerId error:error];
|
||||
} else {
|
||||
[self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot];
|
||||
}
|
||||
};
|
||||
id<FIRListenerRegistration> listener = [_query addSnapshotListener:listenerBlock];
|
||||
_listeners[listenerId] = listener;
|
||||
}
|
||||
}
|
||||
|
||||
- (FIRQuery *)buildQuery {
|
||||
FIRQuery *query = (FIRQuery*)[[RNFirebaseFirestore getFirestoreForApp:_app] collectionWithPath:_path];
|
||||
query = [self applyFilters:query];
|
||||
|
@ -96,6 +131,28 @@
|
|||
return query;
|
||||
}
|
||||
|
||||
- (void)handleQuerySnapshotError:(NSString *)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_COLLECTION_SYNC_EVENT body:event];
|
||||
}
|
||||
|
||||
- (void)handleQuerySnapshotEvent:(NSString *)listenerId
|
||||
querySnapshot:(FIRQuerySnapshot *)querySnapshot {
|
||||
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
|
||||
[event setValue:_app forKey:@"appName"];
|
||||
[event setValue:_path forKey:@"path"];
|
||||
[event setValue:listenerId forKey:@"listenerId"];
|
||||
[event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"];
|
||||
|
||||
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot {
|
||||
NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
|
||||
[snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"];
|
||||
|
|
|
@ -15,15 +15,14 @@
|
|||
@property NSString *app;
|
||||
@property NSString *path;
|
||||
@property FIRDocumentReference *ref;
|
||||
@property NSMutableDictionary *listeners;
|
||||
|
||||
- (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)offSnapshot:(NSString *)listenerId;
|
||||
- (void)onSnapshot:(NSString *)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;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#if __has_include(<Firestore/FIRFirestore.h>)
|
||||
|
||||
static NSMutableDictionary *_listeners;
|
||||
|
||||
- (id)initWithPath:(RCTEventEmitter *)emitter
|
||||
app:(NSString *) app
|
||||
path:(NSString *) path {
|
||||
|
@ -13,6 +15,9 @@
|
|||
_app = app;
|
||||
_path = path;
|
||||
_ref = [[RNFirebaseFirestore getFirestoreForApp:_app] documentWithPath:_path];
|
||||
}
|
||||
// Initialise the static listeners object if required
|
||||
if (!_listeners) {
|
||||
_listeners = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
|
@ -49,7 +54,7 @@
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)offSnapshot:(NSNumber *) listenerId {
|
||||
+ (void)offSnapshot:(NSString *) listenerId {
|
||||
id<FIRListenerRegistration> listener = _listeners[listenerId];
|
||||
if (listener) {
|
||||
[_listeners removeObjectForKey:listenerId];
|
||||
|
@ -57,7 +62,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)onSnapshot:(NSNumber *) listenerId {
|
||||
- (void)onSnapshot:(NSString *) listenerId {
|
||||
if (_listeners[listenerId] == nil) {
|
||||
id listenerBlock = ^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
|
@ -130,7 +135,7 @@
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
- (void)handleDocumentSnapshotError:(NSNumber *)listenerId
|
||||
- (void)handleDocumentSnapshotError:(NSString *)listenerId
|
||||
error:(NSError *)error {
|
||||
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
|
||||
[event setValue:_app forKey:@"appName"];
|
||||
|
@ -141,13 +146,13 @@
|
|||
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
|
||||
}
|
||||
|
||||
- (void)handleDocumentSnapshotEvent:(NSNumber *)listenerId
|
||||
- (void)handleDocumentSnapshotEvent:(NSString *)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"];
|
||||
[event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:@"documentSnapshot"];
|
||||
|
||||
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import CollectionReference from './CollectionReference';
|
|||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import Path from './Path';
|
||||
import INTERNALS from './../../internals';
|
||||
import { firestoreAutoId } from '../../utils';
|
||||
|
||||
export type DeleteOptions = {
|
||||
lastUpdateTime?: string,
|
||||
|
@ -19,9 +20,6 @@ export type WriteResult = {
|
|||
writeTime: string,
|
||||
}
|
||||
|
||||
// track all event registrations
|
||||
let listeners = 0;
|
||||
|
||||
/**
|
||||
* @class DocumentReference
|
||||
*/
|
||||
|
@ -94,12 +92,17 @@ export default class DocumentReference {
|
|||
|
||||
onSnapshot(onNext: Function, onError?: Function): () => void {
|
||||
// TODO: Validation
|
||||
const listenerId = listeners++;
|
||||
const listenerId = firestoreAutoId();
|
||||
|
||||
const listener = (nativeDocumentSnapshot) => {
|
||||
const documentSnapshot = new DocumentSnapshot(this, nativeDocumentSnapshot);
|
||||
onNext(documentSnapshot);
|
||||
};
|
||||
|
||||
// Listen to snapshot events
|
||||
this._firestore.on(
|
||||
this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`),
|
||||
onNext,
|
||||
listener,
|
||||
);
|
||||
|
||||
// Listen for snapshot error events
|
||||
|
@ -115,7 +118,7 @@ export default class DocumentReference {
|
|||
.documentOnSnapshot(this.path, listenerId);
|
||||
|
||||
// Return an unsubscribe method
|
||||
return this._offDocumentSnapshot.bind(this, listenerId, onNext);
|
||||
return this._offDocumentSnapshot.bind(this, listenerId, listener);
|
||||
}
|
||||
|
||||
set(data: { [string]: any }, writeOptions?: WriteOptions): Promise<WriteResult> {
|
||||
|
@ -130,11 +133,14 @@ export default class DocumentReference {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove auth change listener
|
||||
* Remove document snapshot listener
|
||||
* @param listener
|
||||
*/
|
||||
_offDocumentSnapshot(listenerId: number, listener: Function) {
|
||||
this._firestore.log.info('Removing onDocumentSnapshot listener');
|
||||
this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`), listener);
|
||||
this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), listener);
|
||||
this._firestore._native
|
||||
.documentOffSnapshot(this.path, listenerId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import Path from './Path';
|
||||
import QuerySnapshot from './QuerySnapshot';
|
||||
import INTERNALS from './../../internals';
|
||||
import INTERNALS from '../../internals';
|
||||
import { firestoreAutoId } from '../../utils';
|
||||
|
||||
const DIRECTIONS = {
|
||||
ASC: 'ASCENDING',
|
||||
|
@ -51,6 +52,7 @@ export default class Query {
|
|||
_fieldFilters: FieldFilter[];
|
||||
_fieldOrders: FieldOrder[];
|
||||
_firestore: Object;
|
||||
_iid: number;
|
||||
_queryOptions: QueryOptions;
|
||||
_referencePath: Path;
|
||||
|
||||
|
@ -128,7 +130,40 @@ export default class Query {
|
|||
}
|
||||
|
||||
onSnapshot(onNext: () => any, onError?: () => any): () => void {
|
||||
// TODO: Validation
|
||||
const listenerId = firestoreAutoId();
|
||||
|
||||
const listener = (nativeQuerySnapshot) => {
|
||||
const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot);
|
||||
onNext(querySnapshot);
|
||||
};
|
||||
|
||||
// Listen to snapshot events
|
||||
this._firestore.on(
|
||||
this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`),
|
||||
listener,
|
||||
);
|
||||
|
||||
// Listen for snapshot error events
|
||||
if (onError) {
|
||||
this._firestore.on(
|
||||
this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`),
|
||||
onError,
|
||||
);
|
||||
}
|
||||
|
||||
// Add the native listener
|
||||
this._firestore._native
|
||||
.collectionOnSnapshot(
|
||||
this._referencePath.relativeName,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions,
|
||||
listenerId
|
||||
);
|
||||
|
||||
// Return an unsubscribe method
|
||||
return this._offCollectionSnapshot.bind(this, listenerId, listener);
|
||||
}
|
||||
|
||||
orderBy(fieldPath: string, directionStr?: Direction = 'asc'): Query {
|
||||
|
@ -216,4 +251,22 @@ export default class Query {
|
|||
return new Query(this.firestore, this._referencePath, combinedFilters,
|
||||
this._fieldOrders, this._queryOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove query snapshot listener
|
||||
* @param listener
|
||||
*/
|
||||
_offCollectionSnapshot(listenerId: number, listener: Function) {
|
||||
this._firestore.log.info('Removing onQuerySnapshot listener');
|
||||
this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`), listener);
|
||||
this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), listener);
|
||||
this._firestore._native
|
||||
.collectionOffSnapshot(
|
||||
this._referencePath.relativeName,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions,
|
||||
listenerId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ export default class QuerySnapshot {
|
|||
// TODO: Validation
|
||||
// validate.isFunction('callback', callback);
|
||||
|
||||
for (const doc of this.docs) {
|
||||
for (const doc of this._docs) {
|
||||
callback(doc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,19 @@ import INTERNALS from './../../internals';
|
|||
const unquotedIdentifier_ = '(?:[A-Za-z_][A-Za-z_0-9]*)';
|
||||
const UNQUOTED_IDENTIFIER_REGEX = new RegExp(`^${unquotedIdentifier_}$`);
|
||||
|
||||
type CollectionSyncEvent = {
|
||||
appName: string,
|
||||
querySnapshot?: QuerySnapshot,
|
||||
error?: Object,
|
||||
listenerId: string,
|
||||
path: string,
|
||||
}
|
||||
|
||||
type DocumentSyncEvent = {
|
||||
appName: string,
|
||||
document?: DocumentSnapshot,
|
||||
documentSnapshot?: DocumentSnapshot,
|
||||
error?: Object,
|
||||
listenerId: number,
|
||||
listenerId: string,
|
||||
path: string,
|
||||
}
|
||||
|
||||
|
@ -139,11 +147,11 @@ export default class Firestore extends ModuleBase {
|
|||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onCollectionSyncEvent(event: DocumentSyncEvent) {
|
||||
_onCollectionSyncEvent(event: CollectionSyncEvent) {
|
||||
if (event.error) {
|
||||
this.emit(this._getAppEventName(`onCollectionSnapshotError:${event.listenerId}`, event.error));
|
||||
this.emit(this._getAppEventName(`onQuerySnapshotError:${event.listenerId}`), event.error);
|
||||
} else {
|
||||
this.emit(this._getAppEventName(`onCollectionSnapshot:${event.listenerId}`, event.document));
|
||||
this.emit(this._getAppEventName(`onQuerySnapshot:${event.listenerId}`), event.querySnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,8 +164,7 @@ export default class Firestore extends ModuleBase {
|
|||
if (event.error) {
|
||||
this.emit(this._getAppEventName(`onDocumentSnapshotError:${event.listenerId}`), event.error);
|
||||
} else {
|
||||
const snapshot = new DocumentSnapshot(this, event.document);
|
||||
this.emit(this._getAppEventName(`onDocumentSnapshot:${event.listenerId}`), snapshot);
|
||||
this.emit(this._getAppEventName(`onDocumentSnapshot:${event.listenerId}`), event.documentSnapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import sinon from 'sinon';
|
||||
import 'should-sinon';
|
||||
import should from 'should';
|
||||
|
||||
function collectionReferenceTests({ describe, it, context, firebase }) {
|
||||
|
@ -50,6 +52,266 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
|
|||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('calls callback with the initial data and then when document changes', () => {
|
||||
return new Promise(async (resolve) => {
|
||||
const collectionRef = firebase.native.firestore().collection('document-tests');
|
||||
const currentDocValue = { name: 'doc1' };
|
||||
const newDocValue = { name: 'updated' };
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
let unsubscribe;
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribe = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callback(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDocValue);
|
||||
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
await docRef.set(newDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith(newDocValue);
|
||||
callback.should.be.calledTwice();
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribe();
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('calls callback with the initial data and then when document is added', () => {
|
||||
return new Promise(async (resolve) => {
|
||||
const collectionRef = firebase.native.firestore().collection('document-tests');
|
||||
const currentDocValue = { name: 'doc1' };
|
||||
const newDocValue = { name: 'updated' };
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
let unsubscribe;
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribe = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callback(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDocValue);
|
||||
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc2');
|
||||
await docRef.set(newDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith(currentDocValue);
|
||||
callback.should.be.calledWith(newDocValue);
|
||||
callback.should.be.calledThrice();
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribe();
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('doesn\'t call callback when the ref is updated with the same value', async () => {
|
||||
return new Promise(async (resolve) => {
|
||||
const collectionRef = firebase.native.firestore().collection('document-tests');
|
||||
const currentDocValue = { name: 'doc1' };
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
let unsubscribe;
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribe = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callback(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDocValue);
|
||||
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
await docRef.set(currentDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledOnce(); // Callback is not called again
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribe();
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('allows binding multiple callbacks to the same ref', () => {
|
||||
return new Promise(async (resolve) => {
|
||||
// Setup
|
||||
const collectionRef = firebase.native.firestore().collection('document-tests');
|
||||
const currentDocValue = { name: 'doc1' };
|
||||
const newDocValue = { name: 'updated' };
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
|
||||
// Test
|
||||
let unsubscribeA;
|
||||
let unsubscribeB;
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribeA = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callbackA(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribeB = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callbackB(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledWith(currentDocValue);
|
||||
callbackA.should.be.calledOnce();
|
||||
|
||||
callbackB.should.be.calledWith(currentDocValue);
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
await docRef.set(newDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
callbackA.should.be.calledWith(newDocValue);
|
||||
callbackB.should.be.calledWith(newDocValue);
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledTwice();
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribeA();
|
||||
unsubscribeB();
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('listener stops listening when unsubscribed', () => {
|
||||
return new Promise(async (resolve) => {
|
||||
// Setup
|
||||
const collectionRef = firebase.native.firestore().collection('document-tests');
|
||||
const currentDocValue = { name: 'doc1' };
|
||||
const newDocValue = { name: 'updated' };
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
|
||||
// Test
|
||||
let unsubscribeA;
|
||||
let unsubscribeB;
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribeA = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callbackA(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
await new Promise((resolve2) => {
|
||||
unsubscribeB = collectionRef.onSnapshot((snapshot) => {
|
||||
snapshot.forEach(doc => callbackB(doc.data()));
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledWith(currentDocValue);
|
||||
callbackA.should.be.calledOnce();
|
||||
|
||||
callbackB.should.be.calledWith(currentDocValue);
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
await docRef.set(newDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
callbackA.should.be.calledWith(newDocValue);
|
||||
callbackB.should.be.calledWith(newDocValue);
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledTwice();
|
||||
|
||||
// Unsubscribe A
|
||||
|
||||
unsubscribeA();
|
||||
|
||||
await docRef.set(currentDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
callbackB.should.be.calledWith(currentDocValue);
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledThrice();
|
||||
|
||||
// Unsubscribe B
|
||||
|
||||
unsubscribeB();
|
||||
|
||||
await docRef.set(newDocValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
setTimeout(() => resolve2(), 5);
|
||||
});
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledThrice();
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Where
|
||||
context('where()', () => {
|
||||
it('correctly handles == boolean values', () => {
|
||||
|
|
|
@ -49,6 +49,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
|
|||
|
||||
callback.should.be.calledWith(currentDataValue);
|
||||
|
||||
// Update the document
|
||||
|
||||
await docRef.set(newDataValue);
|
||||
|
||||
await new Promise((resolve2) => {
|
||||
|
|
Loading…
Reference in New Issue