2
0
mirror of synced 2025-02-02 09:34:45 +00:00

[database][wip] on/off logic refactor - heavily still wip so things still to change

This commit is contained in:
Salakar 2017-08-14 11:05:49 +01:00
parent 0675aa076d
commit f1709970e9
13 changed files with 574 additions and 459 deletions

View File

@ -81,10 +81,9 @@ public class Utils {
* @param path * @param path
* @param dataSnapshot * @param dataSnapshot
* @param refId * @param refId
* @param listenerId
* @return * @return
*/ */
public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId, int listenerId) { public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId) {
WritableMap snapshot = Arguments.createMap(); WritableMap snapshot = Arguments.createMap();
WritableMap eventMap = Arguments.createMap(); WritableMap eventMap = Arguments.createMap();
@ -111,7 +110,6 @@ public class Utils {
eventMap.putString("path", path); eventMap.putString("path", path);
eventMap.putMap("snapshot", snapshot); eventMap.putMap("snapshot", snapshot);
eventMap.putString("eventName", name); eventMap.putString("eventName", name);
eventMap.putInt("listenerId", listenerId);
eventMap.putString("previousChildName", previousChildName); eventMap.putString("previousChildName", previousChildName);
return eventMap; return eventMap;

View File

@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseApp;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.MutableData; import com.google.firebase.database.MutableData;
import com.google.firebase.database.OnDisconnect; import com.google.firebase.database.OnDisconnect;
import com.google.firebase.database.ServerValue; import com.google.firebase.database.ServerValue;
@ -21,6 +22,7 @@ import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.ValueEventListener;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -31,11 +33,39 @@ import io.invertase.firebase.Utils;
public class RNFirebaseDatabase extends ReactContextBaseJavaModule { public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseDatabase"; private static final String TAG = "RNFirebaseDatabase";
private HashMap<String, ChildEventListener> childEventListeners;
private HashMap<String, ValueEventListener> valueEventListeners;
private SparseArray<RNFirebaseDatabaseReference> references = new SparseArray<>(); private SparseArray<RNFirebaseDatabaseReference> references = new SparseArray<>();
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>(); private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
RNFirebaseDatabase(ReactApplicationContext reactContext) { RNFirebaseDatabase(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
childEventListeners = new HashMap<String, ChildEventListener>();
valueEventListeners = new HashMap<String, ValueEventListener>();
}
Boolean hasValueEventListener(String queryKey) {
return valueEventListeners.containsKey(queryKey);
}
Boolean hasChildEventListener(String queryKey) {
return childEventListeners.containsKey(queryKey);
}
void addValueEventListener(String queryKey, ValueEventListener listener) {
valueEventListeners.put(queryKey, listener);
}
void addChildEventListener(String queryKey, ChildEventListener listener) {
childEventListeners.put(queryKey, listener);
}
void removeValueEventListener(String queryKey) {
valueEventListeners.remove(queryKey);
}
void removeChildEventListener(String queryKey) {
childEventListeners.remove(queryKey);
} }
/* /*
@ -394,17 +424,20 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
/** /**
* Subscribe to real time events for the specified database path + modifiers * Subscribe to real time events for the specified database path + modifiers
* *
* @param appName * @param appName String
* @param refId * @param args ReadableMap
* @param path
* @param modifiers
* @param eventName
* @param promise
*/ */
@ReactMethod @ReactMethod
public void on(String appName, int refId, String path, ReadableArray modifiers, String eventName, Promise promise) { public void on(String appName, ReadableMap args) {
getInternalReferenceForApp(appName, refId, path, modifiers, true).on(eventName); RNFirebaseDatabaseReference ref = getInternalReferenceForApp(
appName,
args.getInt("id"),
args.getString("path"),
args.getArray("modifiers"),
true
);
ref.on(this, args.getString("eventType"), args.getString("eventQueryKey"));
} }
/* /*

View File

@ -1,5 +1,6 @@
package io.invertase.firebase.database; package io.invertase.firebase.database;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.List; import java.util.List;
@ -9,6 +10,7 @@ import android.util.SparseArray;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
@ -30,8 +32,7 @@ public class RNFirebaseDatabaseReference {
private String path; private String path;
private String appName; private String appName;
private ReactContext reactContext; private ReactContext reactContext;
private SparseArray<ChildEventListener> childEventListeners;
private SparseArray<ValueEventListener> valueEventListeners;
Query getQuery() { Query getQuery() {
return query; return query;
@ -49,8 +50,6 @@ public class RNFirebaseDatabaseReference {
appName = app; appName = app;
path = refPath; path = refPath;
reactContext = context; reactContext = context;
childEventListeners = new SparseArray<ChildEventListener>();
valueEventListeners = new SparseArray<ValueEventListener>();
query = buildDatabaseQueryAtPathAndModifiers(path, modifiersArray); query = buildDatabaseQueryAtPathAndModifiers(path, modifiersArray);
} }
@ -63,7 +62,7 @@ public class RNFirebaseDatabaseReference {
ValueEventListener onceValueEventListener = new ValueEventListener() { ValueEventListener onceValueEventListener = new ValueEventListener() {
@Override @Override
public void onDataChange(DataSnapshot dataSnapshot) { public void onDataChange(DataSnapshot dataSnapshot) {
WritableMap data = Utils.snapshotToMap("value", path, dataSnapshot, null, refId, 0); WritableMap data = Utils.snapshotToMap("value", path, dataSnapshot, null, refId);
promise.resolve(data); promise.resolve(data);
} }
@ -90,7 +89,7 @@ public class RNFirebaseDatabaseReference {
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_added".equals(eventName)) { if ("child_added".equals(eventName)) {
query.removeEventListener(this); query.removeEventListener(this);
WritableMap data = Utils.snapshotToMap("child_added", path, dataSnapshot, previousChildName, refId, 0); WritableMap data = Utils.snapshotToMap("child_added", path, dataSnapshot, previousChildName, refId);
promise.resolve(data); promise.resolve(data);
} }
} }
@ -99,7 +98,7 @@ public class RNFirebaseDatabaseReference {
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_changed".equals(eventName)) { if ("child_changed".equals(eventName)) {
query.removeEventListener(this); query.removeEventListener(this);
WritableMap data = Utils.snapshotToMap("child_changed", path, dataSnapshot, previousChildName, refId, 0); WritableMap data = Utils.snapshotToMap("child_changed", path, dataSnapshot, previousChildName, refId);
promise.resolve(data); promise.resolve(data);
} }
} }
@ -108,7 +107,7 @@ public class RNFirebaseDatabaseReference {
public void onChildRemoved(DataSnapshot dataSnapshot) { public void onChildRemoved(DataSnapshot dataSnapshot) {
if ("child_removed".equals(eventName)) { if ("child_removed".equals(eventName)) {
query.removeEventListener(this); query.removeEventListener(this);
WritableMap data = Utils.snapshotToMap("child_removed", path, dataSnapshot, null, refId, 0); WritableMap data = Utils.snapshotToMap("child_removed", path, dataSnapshot, null, refId);
promise.resolve(data); promise.resolve(data);
} }
} }
@ -117,7 +116,7 @@ public class RNFirebaseDatabaseReference {
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_moved".equals(eventName)) { if ("child_moved".equals(eventName)) {
query.removeEventListener(this); query.removeEventListener(this);
WritableMap data = Utils.snapshotToMap("child_moved", path, dataSnapshot, previousChildName, refId, 0); WritableMap data = Utils.snapshotToMap("child_moved", path, dataSnapshot, previousChildName, refId);
promise.resolve(data); promise.resolve(data);
} }
} }
@ -135,14 +134,20 @@ public class RNFirebaseDatabaseReference {
/** /**
* Handles a React Native JS 'on' request and initializes listeners. * Handles a React Native JS 'on' request and initializes listeners.
*
* @param eventName * @param eventName
*/ */
void on(String eventName) { void on(RNFirebaseDatabase database, String eventName, String queryKey) {
if (eventName.equals("value")) {
addValueEventListener(queryKey, database);
} else {
addChildEventListener(queryKey, database, eventName);
}
} }
/** /**
* Handles a React Native JS 'once' request. * Handles a React Native JS 'once' request.
*
* @param eventName * @param eventName
* @param promise * @param promise
*/ */
@ -155,136 +160,153 @@ public class RNFirebaseDatabaseReference {
} }
// todo cleanup all below /**
* @param queryKey
void addChildEventListener(final int listenerId, final String eventName) { * @param eventName
if (childEventListeners.get(listenerId) != null) { */
private void addChildEventListener(final String queryKey, final RNFirebaseDatabase database, final String eventName) {
if (!database.hasChildEventListener(queryKey)) {
ChildEventListener childEventListener = new ChildEventListener() { ChildEventListener childEventListener = new ChildEventListener() {
@Override @Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_added".equals(eventName)) { if ("child_added".equals(eventName)) {
handleDatabaseEvent("child_added", listenerId, dataSnapshot, previousChildName); handleDatabaseEvent("child_added", queryKey, dataSnapshot, previousChildName);
} }
} }
@Override @Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_changed".equals(eventName)) { if ("child_changed".equals(eventName)) {
handleDatabaseEvent("child_changed", listenerId, dataSnapshot, previousChildName); handleDatabaseEvent("child_changed", queryKey, dataSnapshot, previousChildName);
} }
} }
@Override @Override
public void onChildRemoved(DataSnapshot dataSnapshot) { public void onChildRemoved(DataSnapshot dataSnapshot) {
if ("child_removed".equals(eventName)) { if ("child_removed".equals(eventName)) {
handleDatabaseEvent("child_removed", listenerId, dataSnapshot, null); handleDatabaseEvent("child_removed", queryKey, dataSnapshot, null);
} }
} }
@Override @Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
if ("child_moved".equals(eventName)) { if ("child_moved".equals(eventName)) {
handleDatabaseEvent("child_moved", listenerId, dataSnapshot, previousChildName); handleDatabaseEvent("child_moved", queryKey, dataSnapshot, previousChildName);
} }
} }
@Override @Override
public void onCancelled(DatabaseError error) { public void onCancelled(DatabaseError error) {
removeChildEventListener(listenerId); query.removeEventListener(this);
handleDatabaseError(listenerId, error); database.removeChildEventListener(queryKey);
handleDatabaseError(queryKey, error);
} }
}; };
childEventListeners.put(listenerId, childEventListener); database.addChildEventListener(queryKey, childEventListener);
query.addChildEventListener(childEventListener); query.addChildEventListener(childEventListener);
Log.d(TAG, "Added ChildEventListener for refId: " + refId + " listenerId: " + listenerId);
} else {
Log.d(TAG, "ChildEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
} }
} }
void addValueEventListener(final int listenerId) { /**
if (valueEventListeners.get(listenerId) != null) { * @param queryKey
*/
private void addValueEventListener(final String queryKey, final RNFirebaseDatabase database) {
if (!database.hasValueEventListener(queryKey)) {
ValueEventListener valueEventListener = new ValueEventListener() { ValueEventListener valueEventListener = new ValueEventListener() {
@Override @Override
public void onDataChange(DataSnapshot dataSnapshot) { public void onDataChange(DataSnapshot dataSnapshot) {
handleDatabaseEvent("value", listenerId, dataSnapshot, null); handleDatabaseEvent("value", queryKey, dataSnapshot, null);
} }
@Override @Override
public void onCancelled(DatabaseError error) { public void onCancelled(DatabaseError error) {
removeValueEventListener(listenerId); query.removeEventListener(this);
handleDatabaseError(listenerId, error); database.removeValueEventListener(queryKey);
handleDatabaseError(queryKey, error);
} }
}; };
valueEventListeners.put(listenerId, valueEventListener); database.addValueEventListener(queryKey, valueEventListener);
query.addValueEventListener(valueEventListener); query.addValueEventListener(valueEventListener);
Log.d(TAG, "Added ValueEventListener for refId: " + refId + " listenerId: " + listenerId);
} else {
Log.d(TAG, "ValueEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
} }
} }
void removeEventListener(int listenerId, String eventName) { /**
if ("value".equals(eventName)) { * @param name
this.removeValueEventListener(listenerId); * @param dataSnapshot
} else { * @param previousChildName
this.removeChildEventListener(listenerId); */
} private void handleDatabaseEvent(final String name, String queryKey, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
}
boolean hasListeners() {
return childEventListeners.size() > 0 || valueEventListeners.size() > 0;
}
public void cleanup() {
Log.d(TAG, "cleaning up database reference " + this);
this.removeChildEventListener(null);
this.removeValueEventListener(null);
}
private void removeChildEventListener(Integer listenerId) {
ChildEventListener listener = childEventListeners.get(listenerId);
if (listener != null) {
query.removeEventListener(listener);
childEventListeners.delete(listenerId);
}
}
private void removeValueEventListener(Integer listenerId) {
ValueEventListener listener = valueEventListeners.get(listenerId);
if (listener != null) {
query.removeEventListener(listener);
valueEventListeners.delete(listenerId);
}
}
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId, listenerId);
WritableMap evt = Arguments.createMap(); WritableMap evt = Arguments.createMap();
evt.putString("eventName", name); WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId);
evt.putMap("body", data); evt.putMap("body", data);
evt.putInt("refId", refId);
evt.putString("eventName", name);
evt.putString("appName", appName);
evt.putString("queryKey", queryKey);
Utils.sendEvent(reactContext, "database_event", evt); Utils.sendEvent(reactContext, "database_on_event", evt);
} }
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) { /**
* Handles a database listener cancellation error.
*
* @param error
*/
private void handleDatabaseError(String queryKey, final DatabaseError error) {
WritableMap errMap = Arguments.createMap(); WritableMap errMap = Arguments.createMap();
errMap.putInt("refId", refId); errMap.putInt("refId", refId);
if (listenerId != null) {
errMap.putInt("listenerId", listenerId);
}
errMap.putString("path", path); errMap.putString("path", path);
errMap.putString("appName", appName);
errMap.putString("queryKey", queryKey);
errMap.putInt("code", error.getCode()); errMap.putInt("code", error.getCode());
errMap.putString("details", error.getDetails()); errMap.putString("details", error.getDetails());
errMap.putString("message", error.getMessage()); errMap.putString("message", error.getMessage());
Utils.sendEvent(reactContext, "database_error", errMap); Utils.sendEvent(reactContext, "database_cancel_event", errMap);
} }
// todo cleanup below
// void removeEventListener(int listenerId, String eventName) {
// if ("value".equals(eventName)) {
// removeValueEventListener(listenerId);
// } else {
// removeChildEventListener(listenerId);
// }
// }
// boolean hasListeners() {
// return childEventListeners.size() > 0 || valueEventListeners.size() > 0;
// }
//
// public void cleanup() {
// Log.d(TAG, "cleaning up database reference " + this);
// this.removeChildEventListener(null);
// this.removeValueEventListener(null);
// }
// private void removeChildEventListener(Integer listenerId) {
// ChildEventListener listener = childEventListeners.get(listenerId);
// if (listener != null) {
// query.removeEventListener(listener);
// childEventListeners.remove(listenerId);
// }
// }
//
// private void removeValueEventListener(Integer listenerId) {
// ValueEventListener listener = valueEventListeners.get(listenerId);
// if (listener != null) {
// query.removeEventListener(listener);
// valueEventListeners.delete(listenerId);
// }
// }
private Query buildDatabaseQueryAtPathAndModifiers(String path, ReadableArray modifiers) { private Query buildDatabaseQueryAtPathAndModifiers(String path, ReadableArray modifiers) {
FirebaseDatabase firebaseDatabase = RNFirebaseDatabase.getDatabaseForApp(appName); FirebaseDatabase firebaseDatabase = RNFirebaseDatabase.getDatabaseForApp(appName);

View File

@ -8,8 +8,8 @@ static NSString *const AUTH_ERROR_EVENT = @"authError";
static NSString *const AUTH_ANONYMOUS_ERROR_EVENT = @"authAnonymousError"; static NSString *const AUTH_ANONYMOUS_ERROR_EVENT = @"authAnonymousError";
// Database // Database
static NSString *const DATABASE_DATA_EVENT = @"database_event"; static NSString *const DATABASE_ON_EVENT = @"database_on_event";
static NSString *const DATABASE_ERROR_EVENT = @"database_error"; static NSString *const DATABASE_CANCEL_EVENT = @"database_cancel_event";
static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_event"; static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_event";
static NSString *const DATABASE_VALUE_EVENT = @"value"; static NSString *const DATABASE_VALUE_EVENT = @"value";

View File

@ -1,12 +1,15 @@
#import "RNFirebaseDatabase.h" #import "RNFirebaseDatabase.h"
#if __has_include(<FirebaseDatabase/FIRDatabase.h>) #if __has_include(<FirebaseDatabase/FIRDatabase.h>)
#import "RNFirebaseDatabaseReference.h" #import "RNFirebaseDatabaseReference.h"
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
@implementation RNFirebaseDatabase @implementation RNFirebaseDatabase
RCT_EXPORT_MODULE(); RCT_EXPORT_MODULE();
// TODO document methods
- (id)init { - (id)init {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
@ -17,21 +20,28 @@ RCT_EXPORT_MODULE();
return self; return self;
} }
RCT_EXPORT_METHOD(goOnline:(NSString *) appName) { RCT_EXPORT_METHOD(goOnline:
(NSString *) appName) {
[[RNFirebaseDatabase getDatabaseForApp:appName] goOnline]; [[RNFirebaseDatabase getDatabaseForApp:appName] goOnline];
} }
RCT_EXPORT_METHOD(goOffline:(NSString *) appName) { RCT_EXPORT_METHOD(goOffline:
(NSString *) appName) {
[[RNFirebaseDatabase getDatabaseForApp:appName] goOffline]; [[RNFirebaseDatabase getDatabaseForApp:appName] goOffline];
} }
RCT_EXPORT_METHOD(setPersistence:(NSString *) appName RCT_EXPORT_METHOD(setPersistence:
state:(BOOL) state) { (NSString *) appName
state:
(BOOL) state) {
[RNFirebaseDatabase getDatabaseForApp:appName].persistenceEnabled = state; [RNFirebaseDatabase getDatabaseForApp:appName].persistenceEnabled = state;
} }
RCT_EXPORT_METHOD(keepSynced:(NSString *) appName RCT_EXPORT_METHOD(keepSynced:
refId:(nonnull NSNumber *) refId (NSString *) appName
refId:
(nonnull
NSNumber *) refId
path:(NSString *) path path:(NSString *) path
modifiers:(NSArray *) modifiers modifiers:(NSArray *) modifiers
state:(BOOL) state) { state:(BOOL) state) {
@ -40,8 +50,11 @@ RCT_EXPORT_METHOD(keepSynced:(NSString *) appName
[query keepSynced:state]; [query keepSynced:state];
} }
RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appName RCT_EXPORT_METHOD(transactionTryCommit:
transactionId:(nonnull NSNumber *) transactionId (NSString *) appName
transactionId:
(nonnull
NSNumber *) transactionId
updates:(NSDictionary *) updates) { updates:(NSDictionary *) updates) {
__block NSMutableDictionary *transactionState; __block NSMutableDictionary *transactionState;
@ -69,9 +82,13 @@ RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appName
} }
RCT_EXPORT_METHOD(transactionStart:(NSString *) appName RCT_EXPORT_METHOD(transactionStart:
path:(NSString *) path (NSString *) appName
transactionId:(nonnull NSNumber *) transactionId path:
(NSString *) path
transactionId:
(nonnull
NSNumber *) transactionId
applyLocally:(BOOL) applyLocally) { applyLocally:(BOOL) applyLocally) {
dispatch_async(_transactionQueue, ^{ dispatch_async(_transactionQueue, ^{
NSMutableDictionary *transactionState = [NSMutableDictionary new]; NSMutableDictionary *transactionState = [NSMutableDictionary new];
@ -118,105 +135,151 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
}); });
} }
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *) appName RCT_EXPORT_METHOD(onDisconnectSet:
path:(NSString *) path (NSString *) appName
props:(NSDictionary *) props path:
resolver:(RCTPromiseResolveBlock) resolve (NSString *) path
rejecter:(RCTPromiseRejectBlock) reject) { props:
(NSDictionary *) props
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref onDisconnectSetValue:props[@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref onDisconnectSetValue:props[@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(onDisconnectUpdate:(NSString *) appName RCT_EXPORT_METHOD(onDisconnectUpdate:
path:(NSString *) path (NSString *) appName
props:(NSDictionary *) props path:
resolver:(RCTPromiseResolveBlock) resolve (NSString *) path
rejecter:(RCTPromiseRejectBlock) reject) { props:
(NSDictionary *) props
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref onDisconnectUpdateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref onDisconnectUpdateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) appName RCT_EXPORT_METHOD(onDisconnectRemove:
path:(NSString *) path (NSString *) appName
resolver:(RCTPromiseResolveBlock) resolve path:
rejecter:(RCTPromiseRejectBlock) reject) { (NSString *) path
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref onDisconnectRemoveValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref onDisconnectRemoveValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) appName RCT_EXPORT_METHOD(onDisconnectCancel:
path:(NSString *) path (NSString *) appName
resolver:(RCTPromiseResolveBlock) resolve path:
rejecter:(RCTPromiseRejectBlock) reject) { (NSString *) path
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref cancelDisconnectOperationsWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref cancelDisconnectOperationsWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(set:(NSString *) appName RCT_EXPORT_METHOD(set:
path:(NSString *) path (NSString *) appName
props:(NSDictionary *) props path:
resolver:(RCTPromiseResolveBlock) resolve (NSString *) path
rejecter:(RCTPromiseRejectBlock) reject) { props:
(NSDictionary *) props
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref setValue:[props valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref setValue:[props valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(setPriority:(NSString *) appName RCT_EXPORT_METHOD(setPriority:
path:(NSString *) path (NSString *) appName
priority:(NSDictionary *) priority path:
resolver:(RCTPromiseResolveBlock) resolve (NSString *) path
rejecter:(RCTPromiseRejectBlock) reject) { priority:
(NSDictionary *) priority
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) { [ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(setWithPriority:(NSString *) appName RCT_EXPORT_METHOD(setWithPriority:
path:(NSString *) path (NSString *) appName
data:(NSDictionary *) data path:
priority:(NSDictionary *) priority (NSString *) path
resolver:(RCTPromiseResolveBlock) resolve data:
rejecter:(RCTPromiseRejectBlock) reject) { (NSDictionary *) data
priority:
(NSDictionary *) priority
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) { [ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(update:(NSString *) appName RCT_EXPORT_METHOD(update:
path:(NSString *) path (NSString *) appName
props:(NSDictionary *) props path:
resolver:(RCTPromiseResolveBlock) resolve (NSString *) path
rejecter:(RCTPromiseRejectBlock) reject) { props:
(NSDictionary *) props
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref updateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref updateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(remove:(NSString *) appName RCT_EXPORT_METHOD(remove:
path:(NSString *) path (NSString *) appName
resolver:(RCTPromiseResolveBlock) resolve path:
rejecter:(RCTPromiseRejectBlock) reject) { (NSString *) path
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path]; FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
[ref removeValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) { [ref removeValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error]; [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}]; }];
} }
RCT_EXPORT_METHOD(once:(NSString *) appName RCT_EXPORT_METHOD(once:
refId:(nonnull NSNumber *) refId (NSString *) appName
refId:
(nonnull
NSNumber *) refId
path:(NSString *) path path:(NSString *) path
modifiers:(NSArray *) modifiers modifiers:(NSArray *) modifiers
eventName:(NSString *) eventName eventName:(NSString *) eventName
@ -229,9 +292,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
/* /*
* INTERNALS/UTILS * INTERNALS/UTILS
*/ */
+ (void) handlePromise:(RCTPromiseResolveBlock) resolve + (void)handlePromise:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject databaseError:(NSError *)databaseError {
rejecter:(RCTPromiseRejectBlock) reject
databaseError:(NSError *) databaseError {
if (databaseError != nil) { if (databaseError != nil) {
NSDictionary *jsError = [RNFirebaseDatabase getJSError:databaseError]; NSDictionary *jsError = [RNFirebaseDatabase getJSError:databaseError];
reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], databaseError); reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], databaseError);
@ -245,16 +306,11 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
return [FIRDatabase databaseForApp:app]; return [FIRDatabase databaseForApp:app];
} }
- (FIRDatabaseReference *) getReferenceForAppPath:(NSString *) appName - (FIRDatabaseReference *)getReferenceForAppPath:(NSString *)appName path:(NSString *)path {
path:(NSString *) path {
return [[RNFirebaseDatabase getDatabaseForApp:appName] referenceWithPath:path]; return [[RNFirebaseDatabase getDatabaseForApp:appName] referenceWithPath:path];
} }
- (RNFirebaseDatabaseReference *) getInternalReferenceForApp:(NSString *) appName - (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appName refId:(NSNumber *)refId path:(NSString *)path modifiers:(NSArray *)modifiers keep:(BOOL)keep {
refId:(NSNumber *) refId
path:(NSString *) path
modifiers:(NSArray *) modifiers
keep:(BOOL) keep {
RNFirebaseDatabaseReference *ref = _dbReferences[refId]; RNFirebaseDatabaseReference *ref = _dbReferences[refId];
if (ref == nil) { if (ref == nil) {
@ -268,14 +324,11 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
} }
// TODO: Move to error util for use in other modules // TODO: Move to error util for use in other modules
+ (NSString *) getMessageWithService:(NSString *) message + (NSString *)getMessageWithService:(NSString *)message service:(NSString *)service fullCode:(NSString *)fullCode {
service:(NSString *) service
fullCode:(NSString *) fullCode {
return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]]; return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]];
} }
+ (NSString *) getCodeWithService:(NSString *) service + (NSString *)getCodeWithService:(NSString *)service code:(NSString *)code {
code:(NSString *) code {
return [NSString stringWithFormat:@"%@/%@", [service uppercaseString], [code uppercaseString]]; return [NSString stringWithFormat:@"%@/%@", [service uppercaseString], [code uppercaseString]];
} }
@ -352,9 +405,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
return errorMap; return errorMap;
} }
- (NSDictionary *) createTransactionUpdateMap:(NSString *) appName - (NSDictionary *)createTransactionUpdateMap:(NSString *)appName transactionId:(NSNumber *)transactionId updatesData:(FIRMutableData *)updatesData {
transactionId:(NSNumber *) transactionId
updatesData:(FIRMutableData *) updatesData {
NSMutableDictionary *updatesMap = [[NSMutableDictionary alloc] init]; NSMutableDictionary *updatesMap = [[NSMutableDictionary alloc] init];
[updatesMap setValue:transactionId forKey:@"id"]; [updatesMap setValue:transactionId forKey:@"id"];
[updatesMap setValue:@"update" forKey:@"type"]; [updatesMap setValue:@"update" forKey:@"type"];
@ -364,11 +415,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
return updatesMap; return updatesMap;
} }
- (NSDictionary *) createTransactionResultMap:(NSString *) appName - (NSDictionary *)createTransactionResultMap:(NSString *)appName transactionId:(NSNumber *)transactionId error:(NSError *)error committed:(BOOL)committed snapshot:(FIRDataSnapshot *)snapshot {
transactionId:(NSNumber *) transactionId
error:(NSError *) error
committed:(BOOL) committed
snapshot:(FIRDataSnapshot *) snapshot {
NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init]; NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];
[resultMap setValue:transactionId forKey:@"id"]; [resultMap setValue:transactionId forKey:@"id"];
[resultMap setValue:appName forKey:@"appName"]; [resultMap setValue:appName forKey:@"appName"];
@ -388,20 +435,15 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
} }
/* TODO RCT_EXPORT_METHOD(on:(NSString *) appName args:(NSDictionary *) args) {
RCT_EXPORT_METHOD(on: // todo
(nonnull // RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName refId:refId path:path modifiers:modifiers keep:false];
NSNumber *) refId // TODO query identifier
path:(NSString *) path // [ref addEventHandler:listenerId eventName:eventName];
modifiers:(NSArray *) modifiers
listenerId:(nonnull NSNumber *) listenerId
name:(NSString *) eventName
callback:(RCTResponseSenderBlock) callback) {
RNFirebaseDatabaseReference *ref = [self getDBHandle:refId path:path modifiers:modifiers];
[ref addEventHandler:listenerId eventName:eventName];
callback(@[[NSNull null], @{@"status": @"success", @"refId": refId, @"handle": path}]);
} }
/* TODO
RCT_EXPORT_METHOD(off: RCT_EXPORT_METHOD(off:
(nonnull (nonnull
NSNumber *) refId NSNumber *) refId
@ -432,9 +474,9 @@ RCT_EXPORT_METHOD(off:
return ref; return ref;
} */ } */
// Not sure how to get away from this... yet
- (NSArray<NSString *> *)supportedEvents { - (NSArray<NSString *> *)supportedEvents {
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT]; return @[DATABASE_ON_EVENT, DATABASE_CANCEL_EVENT, DATABASE_TRANSACTION_EVENT];
} }
@end @end

View File

@ -28,7 +28,7 @@
if (!_listeners[listenerId]) { if (!_listeners[listenerId]) {
id andPreviousSiblingKeyWithBlock = ^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) { id andPreviousSiblingKeyWithBlock = ^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
NSDictionary *props = [RNFirebaseDatabaseReference snapshotToDict:snapshot]; NSDictionary *props = [RNFirebaseDatabaseReference snapshotToDict:snapshot];
[self sendJSEvent:DATABASE_DATA_EVENT title:eventName props:@{@"eventName": eventName, @"refId": _refId, @"listenerId": listenerId, @"path": _path, @"snapshot": props, @"previousChildName": previousChildName != nil ? previousChildName : [NSNull null]}]; [self sendJSEvent:DATABASE_ON_EVENT title:eventName props:@{@"eventName": eventName, @"refId": _refId, @"listenerId": listenerId, @"path": _path, @"snapshot": props, @"previousChildName": previousChildName != nil ? previousChildName : [NSNull null]}];
}; };
id errorBlock = ^(NSError *_Nonnull error) { id errorBlock = ^(NSError *_Nonnull error) {
NSLog(@"Error onDBEvent: %@", [error debugDescription]); NSLog(@"Error onDBEvent: %@", [error debugDescription]);
@ -130,13 +130,13 @@
} }
- (NSDictionary *)getAndSendDatabaseError:(NSError *)error listenerId:(NSNumber *)listenerId { - (NSDictionary *)getAndSendDatabaseError:(NSError *)error listenerId:(NSNumber *)listenerId {
NSDictionary *event = @{@"eventName": DATABASE_ERROR_EVENT, @"path": _path, @"refId": _refId, @"listenerId": listenerId, @"code": @([error code]), @"details": [error debugDescription], @"message": [error localizedDescription], @"description": [error description]}; NSDictionary *event = @{@"eventName": DATABASE_CANCEL_EVENT, @"path": _path, @"refId": _refId, @"listenerId": listenerId, @"code": @([error code]), @"details": [error debugDescription], @"message": [error localizedDescription], @"description": [error description]};
@try { @try {
[_emitter sendEventWithName:DATABASE_ERROR_EVENT body:event]; [_emitter sendEventWithName:DATABASE_CANCEL_EVENT body:event];
} @catch (NSException *err) { } @catch (NSException *err) {
NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]); NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", DATABASE_ERROR_EVENT, event); NSLog(@"Tried to send: %@ with %@", DATABASE_CANCEL_EVENT, event);
} }
return event; return event;

View File

@ -5,6 +5,7 @@
import { NativeModules } from 'react-native'; import { NativeModules } from 'react-native';
import Reference from './reference'; import Reference from './reference';
import Snapshot from './snapshot';
import TransactionHandler from './transaction'; import TransactionHandler from './transaction';
import ModuleBase from './../../utils/ModuleBase'; import ModuleBase from './../../utils/ModuleBase';
@ -14,6 +15,7 @@ import ModuleBase from './../../utils/ModuleBase';
export default class Database extends ModuleBase { export default class Database extends ModuleBase {
constructor(firebaseApp: Object, options: Object = {}) { constructor(firebaseApp: Object, options: Object = {}) {
super(firebaseApp, options, 'Database', true); super(firebaseApp, options, 'Database', true);
this._references = {};
this._serverTimeOffset = 0; this._serverTimeOffset = 0;
this._transactionHandler = new TransactionHandler(this); this._transactionHandler = new TransactionHandler(this);
@ -21,9 +23,49 @@ export default class Database extends ModuleBase {
this._native.setPersistence(this._options.persistence); this._native.setPersistence(this._options.persistence);
} }
// todo event & error listeners
// todo serverTimeOffset event/listener - make ref natively and switch to events // todo serverTimeOffset event/listener - make ref natively and switch to events
// todo use nativeToJSError for on/off error events // todo use nativeToJSError for on/off error events
this.addListener(
this._getAppEventName('database_cancel_event'),
this._handleCancelEvent.bind(this),
);
this.addListener(
this._getAppEventName('database_on_event'),
this._handleOnEvent.bind(this),
);
}
_handleOnEvent(event) {
console.log('>>>ON-event>>>', event);
const { queryKey, body, refId } = event;
const { snapshot, previousChildName } = body;
const remainingListeners = this.listeners(queryKey);
if (!remainingListeners || !remainingListeners.length) {
this._database._native.off(
_refId,
queryKey,
);
delete this._references[refId];
} else {
const ref = this._references[refId];
if (!ref) {
this._database._native.off(
_refId,
queryKey,
);
} else {
this.emit(queryKey, new Snapshot(ref, snapshot), previousChildName);
}
}
}
_handleCancelEvent(event) {
console.log('>>>CANCEL-event>>>', event);
} }
/** /**

View File

@ -3,6 +3,9 @@
*/ */
import Reference from './reference.js'; import Reference from './reference.js';
import { objectToUniqueId } from './../../utils';
// todo doc methods
/** /**
* @class Query * @class Query
@ -15,6 +18,12 @@ export default class Query {
this._reference = ref; this._reference = ref;
} }
/**
*
* @param name
* @param key
* @return {Reference|*}
*/
orderBy(name: string, key?: string) { orderBy(name: string, key?: string) {
this.modifiers.push({ this.modifiers.push({
type: 'orderBy', type: 'orderBy',
@ -25,6 +34,12 @@ export default class Query {
return this._reference; return this._reference;
} }
/**
*
* @param name
* @param limit
* @return {Reference|*}
*/
limit(name: string, limit: number) { limit(name: string, limit: number) {
this.modifiers.push({ this.modifiers.push({
type: 'limit', type: 'limit',
@ -35,6 +50,13 @@ export default class Query {
return this._reference; return this._reference;
} }
/**
*
* @param name
* @param value
* @param key
* @return {Reference|*}
*/
filter(name: string, value: any, key?: string) { filter(name: string, value: any, key?: string) {
this.modifiers.push({ this.modifiers.push({
type: 'filter', type: 'filter',
@ -47,7 +69,27 @@ export default class Query {
return this._reference; return this._reference;
} }
/**
*
* @return {[*]}
*/
getModifiers(): Array<DatabaseModifier> { getModifiers(): Array<DatabaseModifier> {
return [...this.modifiers]; return [...this.modifiers];
} }
/**
*
* @return {*}
*/
queryIdentifier() {
// convert query modifiers array into an object for generating a unique key
const object = {};
for (let i = 0, len = this.modifiers.length; i < len; i++) {
const { name, type, value } = this.modifiers[i];
object[`${type}-${name}`] = value;
}
return objectToUniqueId(object);
}
} }

View File

@ -1,11 +1,21 @@
/** /**
* @flow * @flow
*/ */
import Query from './query.js'; import Query from './query.js';
import Snapshot from './snapshot'; import Snapshot from './snapshot';
import Disconnect from './disconnect'; import Disconnect from './disconnect';
import INTERNALS from './../../internals';
import ReferenceBase from './../../utils/ReferenceBase'; import ReferenceBase from './../../utils/ReferenceBase';
import { promiseOrCallback, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils'; import {
promiseOrCallback,
isFunction,
isObject,
isString,
tryJSONParse,
tryJSONStringify,
generatePushID,
} from './../../utils';
// Unique Reference ID for native events // Unique Reference ID for native events
let refId = 1; let refId = 1;
@ -61,6 +71,7 @@ export default class Reference extends ReferenceBase {
super(path, database); super(path, database);
this._promise = null; this._promise = null;
this._refId = refId++; this._refId = refId++;
this._listeners = 0;
this._refListeners = {}; this._refListeners = {};
this._database = database; this._database = database;
this._query = new Query(this, path, existingModifiers); this._query = new Query(this, path, existingModifiers);
@ -491,6 +502,17 @@ export default class Reference extends ReferenceBase {
* INTERNALS * INTERNALS
*/ */
/**
* Generate a unique key based on this refs path and query modifiers
* @return {string}
*/
makeQueryKey() {
return `$${this.path}$${this._query.queryIdentifier()}$${this._refId}$${this._listeners}`;
}
/**
* Return instance of db logger
*/
get log() { get log() {
return this._database.log; return this._database.log;
} }
@ -540,219 +562,103 @@ export default class Reference extends ReferenceBase {
} }
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
// todo below methods need refactoring
/** /**
* iOS: Called once with the initial data at the specified location and then once each
* time the data changes. It won't trigger until the entire contents have been
* synchronized.
* *
* Android: (@link https://github.com/invertase/react-native-firebase/issues/92) * @param eventType
* - Array & number values: Called once with the initial data at the specified * @param callback
* location and then twice each time the value changes. * @param cancelCallback
* - Other data types: Called once with the initial data at the specified location * @return {*}
* and once each time the data type changes.
*
* @callback onValueCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot representing data at the location
* specified by the current ref. If location has no data, .val() will return null.
*/ */
// todo:context shouldn't be needed - confirm
/** // todo refId should no longer be required - update native to work without it then remove from js internals
* Called once for each initial child at the specified location and then again on(eventType: string, callback: () => any, cancelCallback: () => any): Function {
* every time a new child is added. if (!eventType) {
* throw new Error('Query.on failed: Function called with 0 arguments. Expects at least 2.');
* @callback onChildAddedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data for the
* relevant child.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* Called once every time a child is removed.
*
* A child will get removed when either:
* - remove() is explicitly called on a child or one of its ancestors
* - set(null) is called on that child or one of its ancestors
* - a child has all of its children removed
* - there is a query in effect which now filters out the child (because it's sort
* order changed or the max limit was hit)
*
* @callback onChildRemovedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the old data for
* the child that was removed.
*/
/**
* Called when a child (or any of its descendants) changes.
*
* A single child_changed event may represent multiple changes to the child.
*
* @callback onChildChangedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting new child contents.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* Called when a child's sort order changes, i.e. its position relative to its
* siblings changes.
*
* @callback onChildMovedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data of the moved
* child.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* @typedef (onValueCallback|onChildAddedCallback|onChildRemovedCallback|onChildChangedCallback|onChildMovedCallback) ReferenceEventCallback
*/
/**
* Called if the event subscription is cancelled because the client does
* not have permission to read this data (or has lost the permission to do so).
*
* @callback onFailureCallback
* @param {Error} error - Object indicating why the failure occurred
*/
/**
* Binds callback handlers to when data changes at the current ref's location.
* The primary method of reading data from a Database.
*
* Callbacks can be unbound using {@link off}.
*
* Event Types:
*
* - value: {@link onValueCallback}.
* - child_added: {@link onChildAddedCallback}
* - child_removed: {@link onChildRemovedCallback}
* - child_changed: {@link onChildChangedCallback}
* - child_moved: {@link onChildMovedCallback}
*
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
* @param {ReferenceEventCallback} successCallback - Function that will be called
* when the event occurs with the new data.
* @param {onFailureCallback=} failureCallbackOrContext - Optional callback that is called
* if the event subscription fails. {@link onFailureCallback}
* @param {*=} context - Optional object to bind the callbacks to when calling them.
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
* convenience if you want to pass an inline function to on() and store it later for
* removing using off().
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
*/
on(eventType: string, successCallback: () => any, failureCallbackOrContext: () => any, context: any) {
if (!eventType) throw new Error('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
if (!ReferenceEventTypes[eventType]) throw new Error('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
if (!successCallback) throw new Error('Query.on failed: Was called with 1 argument. Expects at least 2.');
if (!isFunction(successCallback)) throw new Error('Query.on failed: Second argument must be a valid function.');
if (arguments.length > 2 && !failureCallbackOrContext) throw new Error('Query.on failed: third argument must either be a cancel callback or a context object.');
let _failureCallback;
let _context;
if (context) {
_context = context;
_failureCallback = failureCallbackOrContext;
} else if (isFunction(failureCallbackOrContext)) {
_failureCallback = failureCallbackOrContext;
} else {
_context = failureCallbackOrContext;
} }
if (_failureCallback) { if (!isString(eventType) || !ReferenceEventTypes[eventType]) {
_failureCallback = (error) => { throw new Error(`Query.on failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
if (error.message.startsWith('FirebaseError: permission_denied')) {
// eslint-disable-next-line
error.message = `permission_denied at /${this.path}: Client doesn't have permission to access the desired data.`
} }
failureCallbackOrContext(error); if (!callback) {
}; throw new Error('Query.on failed: Function called with 1 argument. Expects at least 2.');
}
// brb, helping someone
let _successCallback;
if (_context) {
_successCallback = successCallback.bind(_context);
} else {
_successCallback = successCallback;
} }
const listener = { if (!isFunction(callback)) {
listenerId: Object.keys(this._refListeners).length + 1, throw new Error('Query.on failed: Second argument must be a valid function.');
eventName: eventType, }
successCallback: _successCallback,
failureCallback: _failureCallback,
};
this._refListeners[listener.listenerId] = listener; if (cancelCallback && !isFunction(cancelCallback)) {
this._database.on(this, listener); throw new Error('Query.on failed: Function called with 3 arguments, but third optional argument `cancelCallback` was not a function.');
return successCallback; }
const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
INTERNALS.SharedEventEmitter.addListener(eventQueryKey, callback);
if (isFunction(cancelCallback)) {
INTERNALS.SharedEventEmitter.once(`${this.makeQueryKey()}:cancelled`, cancelCallback);
}
// initialise the native listener if not already listening
this._database._native.on({
eventType,
eventQueryKey,
id: this._refId, // todo remove
path: this.path,
modifiers: this._query.getModifiers(),
});
if (!this._database._references[this._refId]) {
this._database._references[this._refId] = this;
}
this._listeners = this._listeners + 1;
return callback;
} }
/** /**
* Detaches a callback attached with on().
* *
* Calling off() on a parent listener will not automatically remove listeners * @param eventType
* registered on child nodes. * @param originalCallback
*
* If on() was called multiple times with the same eventType off() must be
* called multiple times to completely remove it.
*
* If a callback is not specified, all callbacks for the specified eventType
* will be removed. If no eventType or callback is specified, all callbacks
* for the Reference will be removed.
*
* If a context is specified, it too is used as a filter parameter: a callback
* will only be detached if, when it was attached with on(), the same event type,
* callback function and context were provided.
*
* If no callbacks matching the parameters provided are found, no callbacks are
* detached.
*
* @param {('value'|'child_added'|'child_changed'|'child_removed'|'child_moved')=} eventType - Type of event to detach callback for.
* @param {Function=} originalCallback - Original callback passed to on()
* TODO @param {*=} context - The context passed to on() when the callback was bound
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#off}
*/ */
off(eventType?: string = '', originalCallback?: () => any) { off(eventType?: string = '', originalCallback?: () => any) {
// $FlowFixMe if (eventType && (!isString(eventType) || !ReferenceEventTypes[eventType])) {
const listeners: Array<DatabaseListener> = Object.values(this._refListeners); throw new Error(`Query.off failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
let listenersToRemove; }
if (eventType && originalCallback) {
listenersToRemove = listeners.filter((listener) => { if (originalCallback && !isFunction(originalCallback)) {
return listener.eventName === eventType && listener.successCallback === originalCallback; throw new Error('Query.off failed: Function called with 2 arguments, but second optional argument was not a function.');
}); }
// Only remove a single listener as per the web spec
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]]; if (eventType) {
} else if (eventType) { const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
listenersToRemove = listeners.filter((listener) => {
return listener.eventName === eventType; if (originalCallback) {
}); INTERNALS.SharedEventEmitter.removeListener(eventQueryKey, originalCallback);
} else if (originalCallback) {
listenersToRemove = listeners.filter((listener) => {
return listener.successCallback === originalCallback;
});
} else { } else {
listenersToRemove = listeners; INTERNALS.SharedEventEmitter.removeAllListeners(eventQueryKey);
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
}
// check if there's any listeners remaining in the js thread
// if there's isn't then call the native .off method which
// will unsubscribe from the native firebase listeners
const remainingListeners = INTERNALS.SharedEventEmitter.listeners(eventQueryKey);
if (!remainingListeners || !remainingListeners.length) {
this._database._native.off(
this._refId, // todo remove
eventQueryKey,
);
// remove straggling cancellation listeners
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
}
} else {
} }
// Remove the listeners from the reference to prevent memory leaks
listenersToRemove.forEach((listener) => {
delete this._refListeners[listener.listenerId];
});
return this._database.off(this._refId, listenersToRemove, Object.keys(this._refListeners).length);
} }

View File

@ -25,9 +25,9 @@ const NATIVE_MODULE_EVENTS = {
'onAuthStateChanged', 'onAuthStateChanged',
], ],
Database: [ Database: [
'database_on_event',
'database_cancel_event',
'database_transaction_event', 'database_transaction_event',
'database_event',
'database_error',
// 'database_server_offset', // TODO // 'database_server_offset', // TODO
], ],
}; };

View File

@ -17,9 +17,11 @@ const DEFAULT_CHUNK_SIZE = 50;
* @param joiner * @param joiner
* @returns {*} * @returns {*}
*/ */
export function deepGet(object: Object, export function deepGet(
object: Object,
path: string, path: string,
joiner?: string = '/'): any { joiner?: string = '/'
): any {
const keys = path.split(joiner); const keys = path.split(joiner);
let i = 0; let i = 0;
@ -43,9 +45,11 @@ export function deepGet(object: Object,
* @param joiner * @param joiner
* @returns {*} * @returns {*}
*/ */
export function deepExists(object: Object, export function deepExists(
object: Object,
path: string, path: string,
joiner?: string = '/'): boolean { joiner?: string = '/'
): boolean {
const keys = path.split(joiner); const keys = path.split(joiner);
let i = 0; let i = 0;
@ -133,10 +137,12 @@ export function noop(): void {
* @param callback * @param callback
* @private * @private
*/ */
function _delayChunk(collection: Array<*>, function _delayChunk(
collection: Array<*>,
chunkSize: number, chunkSize: number,
operation: Function, operation: Function,
callback: Function): void { callback: Function
): void {
const length = collection.length; const length = collection.length;
const iterations = Math.ceil(length / chunkSize); const iterations = Math.ceil(length / chunkSize);
@ -164,10 +170,12 @@ function _delayChunk(collection: Array<*>,
* @param iterator * @param iterator
* @param cb * @param cb
*/ */
export function each(array: Array<*>, export function each(
array: Array<*>,
chunkSize: number | Function, chunkSize: number | Function,
iterator: Function, iterator: Function,
cb?: Function): void { cb?: Function
): void {
if (typeof chunkSize === 'function') { if (typeof chunkSize === 'function') {
cb = iterator; cb = iterator;
iterator = chunkSize; iterator = chunkSize;
@ -202,10 +210,12 @@ export function typeOf(value: any): string {
* @param cb * @param cb
* @returns {*} * @returns {*}
*/ */
export function map(array: Array<*>, export function map(
array: Array<*>,
chunkSize: number | Function, chunkSize: number | Function,
iterator: Function, iterator: Function,
cb?: Function): void { cb?: Function
): void {
if (typeof chunkSize === 'function') { if (typeof chunkSize === 'function') {
cb = iterator; cb = iterator;
iterator = chunkSize; iterator = chunkSize;
@ -307,6 +317,24 @@ export function nativeWithApp(appName, NativeModule) {
return native; return native;
} }
export function objectToUniqueId(object: Object): String {
if (!isObject(object) || object === null) return JSON.stringify(object);
const keys = Object.keys(object).sort();
let key = '{';
for (let i = 0; i < keys.length; i++) {
if (i !== 0) key += ',';
key += JSON.stringify(keys[i]);
key += ':';
key += objectToUniqueId(object[keys[i]]);
}
key += '}';
return key;
}
/** /**
* Return the existing promise if no callback provided or * Return the existing promise if no callback provided or
* exec the promise and callback if optionalCallback is valid. * exec the promise and callback if optionalCallback is valid.

View File

@ -22,10 +22,11 @@ import priorityTests from './priorityTests';
import DatabaseContents from '../../support/DatabaseContents'; import DatabaseContents from '../../support/DatabaseContents';
const testGroups = [ const testGroups = [
issueSpecificTests, factoryTests, keyTests, parentTests, childTests, rootTests, // issueSpecificTests, factoryTests, keyTests, parentTests, childTests, rootTests,
pushTests, onTests, onValueTests, onChildAddedTests, offTests, onceTests, updateTests, // pushTests, onTests, onValueTests, onChildAddedTests, offTests, onceTests, updateTests,
removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests, // removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
priorityTests, // priorityTests,
onValueTests, onChildAddedTests, offTests,
]; ];
function registerTestSuite(testSuite) { function registerTestSuite(testSuite) {

View File

@ -97,6 +97,7 @@ function onTests({ fdescribe, context, it, firebase, tryCatch }) {
await new Promise((resolve) => { await new Promise((resolve) => {
ref.on('value', (snapshot) => { ref.on('value', (snapshot) => {
console.log('>>> SNAP',snapshot.val())
callback(snapshot.val()); callback(snapshot.val());
resolve(); resolve();
}); });