[js][android] Support multiple listeners on a single ref
This commit is contained in:
parent
e8854f1a2e
commit
ef306162b4
@ -78,12 +78,13 @@ public class Utils {
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param refId
|
||||
* @param listenerId
|
||||
* @param path
|
||||
* @param modifiersString
|
||||
* @param dataSnapshot
|
||||
* @return
|
||||
*/
|
||||
public static WritableMap snapshotToMap(String name, String path, String modifiersString, DataSnapshot dataSnapshot) {
|
||||
public static WritableMap snapshotToMap(String name, int refId, Integer listenerId, String path, DataSnapshot dataSnapshot) {
|
||||
WritableMap snapshot = Arguments.createMap();
|
||||
WritableMap eventMap = Arguments.createMap();
|
||||
|
||||
@ -106,10 +107,13 @@ public class Utils {
|
||||
snapshot.putArray("childKeys", Utils.getChildKeys(dataSnapshot));
|
||||
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
|
||||
|
||||
eventMap.putInt("refId", refId);
|
||||
if (listenerId != null) {
|
||||
eventMap.putInt("listenerId", listenerId);
|
||||
}
|
||||
eventMap.putString("path", path);
|
||||
eventMap.putMap("snapshot", snapshot);
|
||||
eventMap.putString("eventName", name);
|
||||
eventMap.putString("modifiersString", modifiersString);
|
||||
|
||||
return eventMap;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseDatabase";
|
||||
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<>();
|
||||
private HashMap<Integer, RNFirebaseDatabaseReference> mReferences = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseTransactionHandler> mTransactionHandlers = new HashMap<>();
|
||||
private FirebaseDatabase mFirebaseDatabase;
|
||||
|
||||
@ -264,7 +264,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* @param updates
|
||||
*/
|
||||
@ -279,61 +279,60 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void on(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
|
||||
public void on(final int refId, final String path, final ReadableArray modifiers, final int listenerId, final String eventName, final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
||||
|
||||
if (eventName.equals("value")) {
|
||||
ref.addValueEventListener();
|
||||
ref.addValueEventListener(listenerId);
|
||||
} else {
|
||||
ref.addChildEventListener(eventName);
|
||||
ref.addChildEventListener(listenerId, eventName);
|
||||
}
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "success");
|
||||
resp.putInt("refId", refId);
|
||||
resp.putString("handle", path);
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void once(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
|
||||
public void once(final int refId, final String path, final ReadableArray modifiers, final String eventName, final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
||||
ref.addOnceValueEventListener(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
|
||||
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so
|
||||
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventNames, so
|
||||
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
|
||||
* off() should therefore clean *everything* up
|
||||
*/
|
||||
@ReactMethod
|
||||
public void off(
|
||||
final String path,
|
||||
final String modifiersString,
|
||||
final String eventName,
|
||||
final int refId,
|
||||
final ReadableArray listeners,
|
||||
final Callback callback) {
|
||||
|
||||
String key = this.getDBListenerKey(path, modifiersString);
|
||||
RNFirebaseDatabaseReference r = mDBListeners.get(key);
|
||||
RNFirebaseDatabaseReference r = mReferences.get(refId);
|
||||
|
||||
if (r != null) {
|
||||
if (eventName == null || "".equals(eventName)) {
|
||||
r.cleanup();
|
||||
mDBListeners.remove(key);
|
||||
} else {
|
||||
r.removeEventListener(eventName);
|
||||
List<Object> listenersList = Utils.recursivelyDeconstructReadableArray(listeners);
|
||||
|
||||
for (Object l : listenersList) {
|
||||
Map<String, Object> listener = (Map) l;
|
||||
int listenerId = ((Double) listener.get("listenerId")).intValue();
|
||||
String eventName = (String) listener.get("eventName");
|
||||
r.removeEventListener(listenerId, eventName);
|
||||
if (!r.hasListeners()) {
|
||||
mDBListeners.remove(key);
|
||||
mReferences.remove(refId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Removed listener " + path + "/" + modifiersString);
|
||||
Log.d(TAG, "Removed listeners refId: " + refId + " ; count: " + listeners.size());
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("handle", path);
|
||||
resp.putInt("refId", refId);
|
||||
resp.putString("status", "success");
|
||||
resp.putString("modifiersString", modifiersString);
|
||||
//TODO: Remaining listeners
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
|
||||
@ -440,23 +439,19 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
private RNFirebaseDatabaseReference getDBHandle(final String path, final ReadableArray modifiersArray, final String modifiersString) {
|
||||
String key = this.getDBListenerKey(path, modifiersString);
|
||||
RNFirebaseDatabaseReference r = mDBListeners.get(key);
|
||||
private RNFirebaseDatabaseReference getDBHandle(final int refId, final String path,
|
||||
final ReadableArray modifiers) {
|
||||
RNFirebaseDatabaseReference r = mReferences.get(refId);
|
||||
|
||||
if (r == null) {
|
||||
ReactContext ctx = getReactApplicationContext();
|
||||
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString);
|
||||
mDBListeners.put(key, r);
|
||||
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, refId, path, modifiers);
|
||||
mReferences.put(refId, r);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
private String getDBListenerKey(String path, String modifiersString) {
|
||||
return path + " | " + modifiersString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
|
@ -1,9 +1,11 @@
|
||||
package io.invertase.firebase.database;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
@ -25,81 +27,87 @@ public class RNFirebaseDatabaseReference {
|
||||
private static final String TAG = "RNFirebaseDBReference";
|
||||
|
||||
private Query mQuery;
|
||||
private int mRefId;
|
||||
private String mPath;
|
||||
private String mModifiersString;
|
||||
private ChildEventListener mEventListener;
|
||||
private ValueEventListener mValueListener;
|
||||
private Map<Integer, ChildEventListener> mChildEventListeners = new HashMap<>();
|
||||
private Map<Integer, ValueEventListener> mValueEventListeners = new HashMap<>();
|
||||
private ReactContext mReactContext;
|
||||
private Set<String> childEventListeners = new HashSet<>();
|
||||
|
||||
public RNFirebaseDatabaseReference(final ReactContext context,
|
||||
final FirebaseDatabase firebaseDatabase,
|
||||
final String path,
|
||||
final ReadableArray modifiersArray,
|
||||
final String modifiersString) {
|
||||
final FirebaseDatabase firebaseDatabase,
|
||||
final int refId,
|
||||
final String path,
|
||||
final ReadableArray modifiersArray) {
|
||||
mReactContext = context;
|
||||
mRefId = refId;
|
||||
mPath = path;
|
||||
mModifiersString = modifiersString;
|
||||
mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray);
|
||||
}
|
||||
|
||||
public void addChildEventListener(final String eventName) {
|
||||
if (mEventListener == null) {
|
||||
mEventListener = new ChildEventListener() {
|
||||
public void addChildEventListener(final int listenerId, final String eventName) {
|
||||
if (!mChildEventListeners.containsKey(listenerId)) {
|
||||
ChildEventListener childEventListener = new ChildEventListener() {
|
||||
@Override
|
||||
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_added", dataSnapshot);
|
||||
if ("child_added".equals(eventName)) {
|
||||
handleDatabaseEvent("child_added", listenerId, dataSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_changed", dataSnapshot);
|
||||
if ("child_changed".equals(eventName)) {
|
||||
handleDatabaseEvent("child_changed", listenerId, dataSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||
handleDatabaseEvent("child_removed", dataSnapshot);
|
||||
if ("child_removed".equals(eventName)) {
|
||||
handleDatabaseEvent("child_removed", listenerId, dataSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_moved", dataSnapshot);
|
||||
if ("child_moved".equals(eventName)) {
|
||||
handleDatabaseEvent("child_moved", listenerId, dataSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
removeChildEventListener();
|
||||
handleDatabaseError(error);
|
||||
removeChildEventListener(listenerId);
|
||||
handleDatabaseError(listenerId, error);
|
||||
}
|
||||
};
|
||||
mQuery.addChildEventListener(mEventListener);
|
||||
Log.d(TAG, "Added ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
mChildEventListeners.put(listenerId, childEventListener);
|
||||
mQuery.addChildEventListener(childEventListener);
|
||||
Log.d(TAG, "Added ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId);
|
||||
} else {
|
||||
Log.w(TAG, "Trying to add duplicate ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
Log.d(TAG, "ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
|
||||
}
|
||||
//Keep track of the events that the JS is interested in knowing about
|
||||
childEventListeners.add(eventName);
|
||||
}
|
||||
|
||||
public void addValueEventListener() {
|
||||
if (mValueListener == null) {
|
||||
mValueListener = new ValueEventListener() {
|
||||
public void addValueEventListener(final int listenerId) {
|
||||
if (!mValueEventListeners.containsKey(listenerId)) {
|
||||
ValueEventListener valueEventListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
handleDatabaseEvent("value", dataSnapshot);
|
||||
handleDatabaseEvent("value", listenerId, dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
removeValueEventListener();
|
||||
handleDatabaseError(error);
|
||||
removeValueEventListener(listenerId);
|
||||
handleDatabaseError(listenerId, error);
|
||||
}
|
||||
};
|
||||
mQuery.addValueEventListener(mValueListener);
|
||||
Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
//this.setListeningTo(mPath, modifiersString, "value");
|
||||
mValueEventListeners.put(listenerId, valueEventListener);
|
||||
mQuery.addValueEventListener(valueEventListener);
|
||||
Log.d(TAG, "Added ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId);
|
||||
} else {
|
||||
Log.w(TAG, "Trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
Log.d(TAG, "ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,63 +115,73 @@ public class RNFirebaseDatabaseReference {
|
||||
final ValueEventListener onceValueEventListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.snapshotToMap("value", mPath, mModifiersString, dataSnapshot);
|
||||
WritableMap data = Utils.snapshotToMap("value", mRefId, null, mPath, dataSnapshot);
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("refId", mRefId);
|
||||
err.putString("path", mPath);
|
||||
err.putInt("code", error.getCode());
|
||||
err.putString("modifiers", mModifiersString);
|
||||
err.putString("details", error.getDetails());
|
||||
err.putString("message", error.getMessage());
|
||||
callback.invoke(err);
|
||||
}
|
||||
};
|
||||
mQuery.addListenerForSingleValueEvent(onceValueEventListener);
|
||||
Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString);
|
||||
Log.d(TAG, "Added OnceValueEventListener for refId: " + mRefId);
|
||||
}
|
||||
|
||||
public void removeEventListener(String eventName) {
|
||||
public void removeEventListener(int listenerId, String eventName) {
|
||||
if ("value".equals(eventName)) {
|
||||
this.removeValueEventListener();
|
||||
this.removeValueEventListener(listenerId);
|
||||
} else {
|
||||
childEventListeners.remove(eventName);
|
||||
if (childEventListeners.isEmpty()) {
|
||||
this.removeChildEventListener();
|
||||
}
|
||||
this.removeChildEventListener(listenerId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasListeners() {
|
||||
return mEventListener != null || mValueListener != null;
|
||||
return !mChildEventListeners.isEmpty() || !mValueEventListeners.isEmpty();
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
Log.d(TAG, "cleaning up database reference " + this);
|
||||
childEventListeners.clear();
|
||||
this.removeChildEventListener();
|
||||
this.removeValueEventListener();
|
||||
this.removeChildEventListener(null);
|
||||
this.removeValueEventListener(null);
|
||||
}
|
||||
|
||||
private void removeChildEventListener() {
|
||||
if (mEventListener != null) {
|
||||
mQuery.removeEventListener(mEventListener);
|
||||
mEventListener = null;
|
||||
private void removeChildEventListener(Integer listenerId) {
|
||||
if (listenerId != null) {
|
||||
ChildEventListener listener = mChildEventListeners.remove(listenerId);
|
||||
if (listener != null) {
|
||||
mQuery.removeEventListener(listener);
|
||||
}
|
||||
} else {
|
||||
for (ChildEventListener listener : mChildEventListeners.values()) {
|
||||
mQuery.removeEventListener(listener);
|
||||
}
|
||||
mChildEventListeners = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeValueEventListener() {
|
||||
if (mValueListener != null) {
|
||||
mQuery.removeEventListener(mValueListener);
|
||||
mValueListener = null;
|
||||
private void removeValueEventListener(Integer listenerId) {
|
||||
if (listenerId != null) {
|
||||
ValueEventListener listener = mValueEventListeners.remove(listenerId);
|
||||
if (listener != null) {
|
||||
mQuery.removeEventListener(listener);
|
||||
}
|
||||
} else {
|
||||
for (ValueEventListener listener : mValueEventListeners.values()) {
|
||||
mQuery.removeEventListener(listener);
|
||||
}
|
||||
mValueEventListeners = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.snapshotToMap(name, mPath, mModifiersString, dataSnapshot);
|
||||
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.snapshotToMap(name, mRefId, listenerId, mPath, dataSnapshot);
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", name);
|
||||
evt.putMap("body", data);
|
||||
@ -171,12 +189,15 @@ public class RNFirebaseDatabaseReference {
|
||||
Utils.sendEvent(mReactContext, "database_event", evt);
|
||||
}
|
||||
|
||||
private void handleDatabaseError(final DatabaseError error) {
|
||||
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) {
|
||||
WritableMap errMap = Arguments.createMap();
|
||||
|
||||
errMap.putInt("refId", mRefId);
|
||||
if (listenerId != null) {
|
||||
errMap.putInt("listenerId", listenerId);
|
||||
}
|
||||
errMap.putString("path", mPath);
|
||||
errMap.putInt("code", error.getCode());
|
||||
errMap.putString("modifiers", mModifiersString);
|
||||
errMap.putString("details", error.getDetails());
|
||||
errMap.putString("message", error.getMessage());
|
||||
|
||||
@ -187,104 +208,102 @@ public class RNFirebaseDatabaseReference {
|
||||
final String path,
|
||||
final ReadableArray modifiers) {
|
||||
Query query = firebaseDatabase.getReference(path);
|
||||
List<Object> strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers);
|
||||
List<Object> modifiersList = Utils.recursivelyDeconstructReadableArray(modifiers);
|
||||
|
||||
for (Object strModifier : strModifiers) {
|
||||
String str = (String) strModifier;
|
||||
for (Object m : modifiersList) {
|
||||
Map<String, Object> modifier = (Map) m;
|
||||
String type = (String) modifier.get("type");
|
||||
String name = (String) modifier.get("name");
|
||||
|
||||
String[] strArr = str.split(":");
|
||||
String methStr = strArr[0];
|
||||
|
||||
if (methStr.equalsIgnoreCase("orderByKey")) {
|
||||
query = query.orderByKey();
|
||||
} else if (methStr.equalsIgnoreCase("orderByValue")) {
|
||||
query = query.orderByValue();
|
||||
} else if (methStr.equalsIgnoreCase("orderByPriority")) {
|
||||
query = query.orderByPriority();
|
||||
} else if (methStr.contains("orderByChild")) {
|
||||
String key = strArr[1];
|
||||
Log.d(TAG, "orderByChild: " + key);
|
||||
query = query.orderByChild(key);
|
||||
} else if (methStr.contains("limitToLast")) {
|
||||
String key = strArr[1];
|
||||
int limit = Integer.parseInt(key);
|
||||
Log.d(TAG, "limitToLast: " + limit);
|
||||
query = query.limitToLast(limit);
|
||||
} else if (methStr.contains("limitToFirst")) {
|
||||
String key = strArr[1];
|
||||
int limit = Integer.parseInt(key);
|
||||
Log.d(TAG, "limitToFirst: " + limit);
|
||||
query = query.limitToFirst(limit);
|
||||
} else if (methStr.contains("equalTo")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.equalTo(doubleValue);
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(booleanValue, strArr[3]);
|
||||
} else {
|
||||
query = query.equalTo(booleanValue);
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(value, strArr[3]);
|
||||
} else {
|
||||
query = query.equalTo(value);
|
||||
}
|
||||
if ("orderBy".equals(type)) {
|
||||
if ("orderByKey".equals(name)) {
|
||||
query = query.orderByKey();
|
||||
} else if ("orderByPriority".equals(name)) {
|
||||
query = query.orderByPriority();
|
||||
} else if ("orderByValue".equals(name)) {
|
||||
query = query.orderByValue();
|
||||
} else if ("orderByChild".equals(name)) {
|
||||
String key = (String) modifier.get("key");
|
||||
query = query.orderByChild(key);
|
||||
}
|
||||
} else if (methStr.contains("endAt")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.endAt(doubleValue);
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(booleanValue, strArr[3]);
|
||||
} else {
|
||||
query = query.endAt(booleanValue);
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(value, strArr[3]);
|
||||
} else {
|
||||
query = query.endAt(value);
|
||||
}
|
||||
} else if ("limit".equals(type)) {
|
||||
int limit = (Integer) modifier.get("limit");
|
||||
if ("limitToLast".equals(name)) {
|
||||
query = query.limitToLast(limit);
|
||||
} else if ("limitToFirst".equals(name)) {
|
||||
query = query.limitToFirst(limit);
|
||||
}
|
||||
} else if (methStr.contains("startAt")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.startAt(doubleValue);
|
||||
} else if ("filter".equals(type)) {
|
||||
String valueType = (String) modifier.get("valueType");
|
||||
String key = (String) modifier.get("key");
|
||||
if ("equalTo".equals(name)) {
|
||||
if ("number".equals(valueType)) {
|
||||
double value = (Double) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("boolean".equals(valueType)) {
|
||||
boolean value = (Boolean) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("string".equals(valueType)) {
|
||||
String value = (String) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(booleanValue, strArr[3]);
|
||||
} else {
|
||||
query = query.startAt(booleanValue);
|
||||
} else if ("endAt".equals(name)) {
|
||||
if ("number".equals(valueType)) {
|
||||
double value = (Double) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("boolean".equals(valueType)) {
|
||||
boolean value = (Boolean) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("string".equals(valueType)) {
|
||||
String value = (String) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(value, strArr[3]);
|
||||
} else {
|
||||
query = query.startAt(value);
|
||||
} else if ("startAt".equals(name)) {
|
||||
if ("number".equals(valueType)) {
|
||||
double value = (Double) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("boolean".equals(valueType)) {
|
||||
boolean value = (Boolean) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
} else if ("string".equals(valueType)) {
|
||||
String value = (String) modifier.get("value");
|
||||
if (key == null) {
|
||||
query = query.equalTo(value);
|
||||
} else {
|
||||
query = query.equalTo(value, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
lib/flow.js
16
lib/flow.js
@ -15,6 +15,22 @@ declare type CredentialType = {
|
||||
secret: string
|
||||
};
|
||||
|
||||
declare type DatabaseListener = {
|
||||
listenerId: number;
|
||||
eventName: string;
|
||||
successCallback: Function;
|
||||
failureCallback?: Function;
|
||||
};
|
||||
|
||||
declare type DatabaseModifier = {
|
||||
type: 'orderBy' | 'limit' | 'filter';
|
||||
name?: string;
|
||||
key?: string;
|
||||
limit?: number;
|
||||
value?: any;
|
||||
valueType?: string;
|
||||
};
|
||||
|
||||
declare type GoogleApiAvailabilityType = {
|
||||
status: number,
|
||||
isAvailable: boolean,
|
||||
|
@ -19,9 +19,8 @@ const FirebaseDatabaseEvt = new NativeEventEmitter(FirebaseDatabase);
|
||||
export default class Database extends Base {
|
||||
constructor(firebase: Object, options: Object = {}) {
|
||||
super(firebase, options);
|
||||
this.subscriptions = {};
|
||||
this.references = {};
|
||||
this.serverTimeOffset = 0;
|
||||
this.errorSubscriptions = {};
|
||||
this.persistenceEnabled = false;
|
||||
this.namespace = 'firebase:database';
|
||||
this.transaction = new TransactionHandler(firebase, this, FirebaseDatabaseEvt);
|
||||
@ -68,20 +67,12 @@ export default class Database extends Base {
|
||||
* @param errorCb
|
||||
* @returns {*}
|
||||
*/
|
||||
on(path: string, modifiersString: string, modifiers: Array<string>, eventName: string, cb: () => void, errorCb: () => void) {
|
||||
const handle = this._handle(path, modifiersString);
|
||||
this.log.debug('adding on listener', handle);
|
||||
|
||||
if (!this.subscriptions[handle]) this.subscriptions[handle] = {};
|
||||
if (!this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName] = [];
|
||||
this.subscriptions[handle][eventName].push(cb);
|
||||
|
||||
if (errorCb) {
|
||||
if (!this.errorSubscriptions[handle]) this.errorSubscriptions[handle] = [];
|
||||
this.errorSubscriptions[handle].push(errorCb);
|
||||
}
|
||||
|
||||
return promisify('on', FirebaseDatabase)(path, modifiersString, modifiers, eventName);
|
||||
on(ref: Reference, listener: DatabaseListener) {
|
||||
const { refId, path, query } = ref;
|
||||
const { listenerId, eventName } = listener;
|
||||
this.log.debug('on() : ', ref.refId, listenerId, eventName);
|
||||
this.references[refId] = ref;
|
||||
return promisify('on', FirebaseDatabase)(refId, path, query.getModifiers(), listenerId, eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,49 +83,30 @@ export default class Database extends Base {
|
||||
* @param origCB
|
||||
* @returns {*}
|
||||
*/
|
||||
off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) {
|
||||
const handle = this._handle(path, modifiersString);
|
||||
this.log.debug('off() : ', handle, eventName);
|
||||
off(refId: number, listeners: Array<DatabaseListener>, remainingListenersCount: number) {
|
||||
this.log.debug('off() : ', refId, listeners);
|
||||
|
||||
if (!this.subscriptions[handle] || (eventName && !this.subscriptions[handle][eventName])) {
|
||||
this.log.warn('off() called, but not currently listening at that location (bad path)', handle, eventName);
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Delete the reference if there are no more listeners
|
||||
if (remainingListenersCount === 0) delete this.references[refId];
|
||||
|
||||
if (eventName && origCB) {
|
||||
const i = this.subscriptions[handle][eventName].indexOf(origCB);
|
||||
if (listeners.length === 0) return Promise.resolve();
|
||||
|
||||
if (i === -1) {
|
||||
this.log.warn('off() called, but the callback specified is not listening at that location (bad path)', handle, eventName);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.subscriptions[handle][eventName].splice(i, 1);
|
||||
if (this.subscriptions[handle][eventName].length > 0) return Promise.resolve();
|
||||
} else if (eventName) {
|
||||
this.subscriptions[handle][eventName] = [];
|
||||
} else {
|
||||
this.subscriptions[handle] = {};
|
||||
}
|
||||
this.errorSubscriptions[handle] = [];
|
||||
return promisify('off', FirebaseDatabase)(path, modifiersString, eventName);
|
||||
return promisify('off', FirebaseDatabase)(refId, listeners.map(listener => ({
|
||||
listenerId: listener.listenerId,
|
||||
eventName: listener.eventName,
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event handlers and their native subscriptions
|
||||
* Removes all references and their native listeners
|
||||
* @returns {Promise.<*>}
|
||||
*/
|
||||
cleanup() {
|
||||
const promises = [];
|
||||
Object.keys(this.subscriptions).forEach((handle) => {
|
||||
Object.keys(this.subscriptions[handle]).forEach((eventName) => {
|
||||
const separator = handle.indexOf('|');
|
||||
const path = handle.substring(0, separator);
|
||||
const modifiersString = handle.substring(separator + 1);
|
||||
promises.push(this.off(path, modifiersString, eventName));
|
||||
});
|
||||
Object.keys(this.references).forEach((refId) => {
|
||||
const ref = this.references[refId];
|
||||
promises.push(this.off(refId, ref.listeners, 0));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
@ -169,18 +141,6 @@ export default class Database extends Base {
|
||||
return Promise.reject({ status: 'Already enabled' });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path
|
||||
* @param modifiersString
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
_handle(path: string = '', modifiersString: string = '') {
|
||||
return `${path}|${modifiersString}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
@ -188,18 +148,14 @@ export default class Database extends Base {
|
||||
*/
|
||||
_handleDatabaseEvent(event: Object) {
|
||||
const body = event.body || {};
|
||||
const { path, modifiersString, eventName, snapshot } = body;
|
||||
const handle = this._handle(path, modifiersString);
|
||||
|
||||
this.log.debug('_handleDatabaseEvent: ', handle, eventName, snapshot && snapshot.key);
|
||||
|
||||
if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) {
|
||||
this.subscriptions[handle][eventName].forEach((cb) => {
|
||||
cb(new Snapshot(new Reference(this, path, modifiersString.split('|')), snapshot), body);
|
||||
});
|
||||
const { refId, listenerId, path, eventName, snapshot } = body;
|
||||
this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key);
|
||||
if (this.references[refId] && this.references[refId].listeners[listenerId]) {
|
||||
const cb = this.references[refId].listeners[listenerId].successCallback;
|
||||
cb(new Snapshot(this.references[refId], snapshot));
|
||||
} else {
|
||||
FirebaseDatabase.off(path, modifiersString, eventName, () => {
|
||||
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', handle, eventName);
|
||||
FirebaseDatabase.off(refId, [{ listenerId, eventName }], () => {
|
||||
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -235,13 +191,15 @@ export default class Database extends Base {
|
||||
* @private
|
||||
*/
|
||||
_handleDatabaseError(error: Object = {}) {
|
||||
const { path, modifiers } = error;
|
||||
const handle = this._handle(path, modifiers);
|
||||
const { refId, listenerId, path } = error;
|
||||
const firebaseError = this._toFirebaseError(error);
|
||||
|
||||
this.log.debug('_handleDatabaseError ->', handle, 'database_error', error);
|
||||
this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error);
|
||||
|
||||
if (this.errorSubscriptions[handle]) this.errorSubscriptions[handle].forEach(listener => listener(firebaseError));
|
||||
if (this.references[refId] && this.references[refId].listeners[listenerId]) {
|
||||
const failureCb = this.references[refId].listeners[listenerId].failureCallback;
|
||||
if (failureCb) failureCb(firebaseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,4 +208,3 @@ export const statics = {
|
||||
TIMESTAMP: FirebaseDatabase.serverValueTimestamp || { '.sv': 'timestamp' },
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -9,47 +9,41 @@ import Reference from './reference.js';
|
||||
* @class Query
|
||||
*/
|
||||
export default class Query extends ReferenceBase {
|
||||
static ref: Reference;
|
||||
modifiers: Array<DatabaseModifier>;
|
||||
|
||||
static modifiers: Array<string>;
|
||||
|
||||
ref: Reference;
|
||||
|
||||
constructor(ref: Reference, path: string, existingModifiers?: Array<string>) {
|
||||
constructor(ref: Reference, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
||||
super(ref.database, path);
|
||||
this.log.debug('creating Query ', path, existingModifiers);
|
||||
this.ref = ref;
|
||||
this.modifiers = existingModifiers ? [...existingModifiers] : [];
|
||||
}
|
||||
|
||||
setOrderBy(name: string, key?: string) {
|
||||
if (key) {
|
||||
this.modifiers.push(`${name}:${key}`);
|
||||
} else {
|
||||
this.modifiers.push(name);
|
||||
}
|
||||
orderBy(name: string, key?: string) {
|
||||
this.modifiers.push({
|
||||
type: 'orderBy',
|
||||
name,
|
||||
key,
|
||||
});
|
||||
}
|
||||
|
||||
setLimit(name: string, limit: number) {
|
||||
this.modifiers.push(`${name}:${limit}`);
|
||||
limit(name: string, limit: number) {
|
||||
this.modifiers.push({
|
||||
type: 'limit',
|
||||
name,
|
||||
limit,
|
||||
});
|
||||
}
|
||||
|
||||
setFilter(name: string, value: any, key?:string) {
|
||||
if (key) {
|
||||
this.modifiers.push(`${name}:${value}:${typeof value}:${key}`);
|
||||
} else {
|
||||
this.modifiers.push(`${name}:${value}:${typeof value}`);
|
||||
}
|
||||
filter(name: string, value: any, key?:string) {
|
||||
this.modifiers.push({
|
||||
type: 'filter',
|
||||
name,
|
||||
value,
|
||||
valueType: typeof value,
|
||||
key,
|
||||
});
|
||||
}
|
||||
|
||||
getModifiers(): Array<string> {
|
||||
getModifiers(): Array<DatabaseModifier> {
|
||||
return [...this.modifiers];
|
||||
}
|
||||
|
||||
getModifiersString(): string {
|
||||
if (!this.modifiers || !Array.isArray(this.modifiers)) {
|
||||
return '';
|
||||
}
|
||||
return this.modifiers.join('|');
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import { ReferenceBase } from './../base';
|
||||
import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
|
||||
|
||||
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
|
||||
// Unique Reference ID for native events
|
||||
let refId = 1;
|
||||
|
||||
/**
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
|
||||
@ -17,15 +19,19 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
|
||||
*/
|
||||
export default class Reference extends ReferenceBase {
|
||||
|
||||
refId: number;
|
||||
listeners: { [listenerId: number]: DatabaseListener };
|
||||
database: FirebaseDatabase;
|
||||
query: Query;
|
||||
|
||||
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<string>) {
|
||||
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
||||
super(database.firebase, path);
|
||||
this.refId = refId++;
|
||||
this.listeners = {};
|
||||
this.database = database;
|
||||
this.namespace = 'firebase:db:ref';
|
||||
this.query = new Query(this, path, existingModifiers);
|
||||
this.log.debug('Created new Reference', this.database._handle(path, existingModifiers));
|
||||
this.log.debug('Created new Reference', this.refId, this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +87,6 @@ export default class Reference extends ReferenceBase {
|
||||
|
||||
const path = this.path;
|
||||
const _value = this._serializeAnyType(value);
|
||||
|
||||
return promisify('push', FirebaseDatabase)(path, _value)
|
||||
.then(({ ref }) => {
|
||||
const newRef = new Reference(this.database, ref);
|
||||
@ -95,36 +100,37 @@ export default class Reference extends ReferenceBase {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventType
|
||||
* @param eventName
|
||||
* @param successCallback
|
||||
* @param failureCallback
|
||||
* @param context TODO
|
||||
* @returns {*}
|
||||
*/
|
||||
on(eventType: string, successCallback: () => any, failureCallback: () => any) {
|
||||
on(eventName: string, successCallback: () => any, failureCallback: () => any) {
|
||||
if (!isFunction(successCallback)) throw new Error('The specified callback must be a function');
|
||||
if (failureCallback && !isFunction(failureCallback)) throw new Error('The specified error callback must be a function');
|
||||
const path = this.path;
|
||||
const modifiers = this.query.getModifiers();
|
||||
const modifiersString = this.query.getModifiersString();
|
||||
this.log.debug('adding reference.on', path, modifiersString, eventType);
|
||||
this.database.on(path, modifiersString, modifiers, eventType, successCallback, failureCallback);
|
||||
this.log.debug('adding reference.on', this.refId, eventName);
|
||||
const listener = {
|
||||
listenerId: Object.keys(this.listeners).length + 1,
|
||||
eventName,
|
||||
successCallback,
|
||||
failureCallback,
|
||||
};
|
||||
this.listeners[listener.listenerId] = listener;
|
||||
this.database.on(this, listener);
|
||||
return successCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventType
|
||||
* @param eventName
|
||||
* @param successCallback
|
||||
* @param failureCallback
|
||||
* @param context TODO
|
||||
* @returns {Promise.<TResult>}
|
||||
*/
|
||||
once(eventType: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: Error) => void) {
|
||||
const path = this.path;
|
||||
const modifiers = this.query.getModifiers();
|
||||
const modifiersString = this.query.getModifiersString();
|
||||
return promisify('once', FirebaseDatabase)(path, modifiersString, modifiers, eventType)
|
||||
once(eventName: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: FirebaseError) => void) {
|
||||
return promisify('once', FirebaseDatabase)(this.refId, this.path, this.query.getModifiers(), eventName)
|
||||
.then(({ snapshot }) => new Snapshot(this, snapshot))
|
||||
.then((snapshot) => {
|
||||
if (isFunction(successCallback)) successCallback(snapshot);
|
||||
@ -139,15 +145,35 @@ export default class Reference extends ReferenceBase {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventType
|
||||
* @param eventName
|
||||
* @param origCB
|
||||
* @returns {*}
|
||||
*/
|
||||
off(eventType?: string = '', origCB?: () => any) {
|
||||
const path = this.path;
|
||||
const modifiersString = this.query.getModifiersString();
|
||||
this.log.debug('ref.off(): ', path, modifiersString, eventType);
|
||||
return this.database.off(path, modifiersString, eventType, origCB);
|
||||
off(eventName?: string = '', origCB?: () => any) {
|
||||
this.log.debug('ref.off(): ', this.refId, eventName);
|
||||
let listenersToRemove;
|
||||
if (eventName && origCB) {
|
||||
listenersToRemove = Object.values(this.listeners).filter((listener) => {
|
||||
return listener.eventName === eventName && listener.successCallback === origCB;
|
||||
});
|
||||
// Only remove a single listener as per the web spec
|
||||
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
|
||||
} else if (eventName) {
|
||||
listenersToRemove = Object.values(this.listeners).filter((listener) => {
|
||||
return listener.eventName === eventName;
|
||||
});
|
||||
} else if (origCB) {
|
||||
listenersToRemove = Object.values(this.listeners).filter((listener) => {
|
||||
return listener.successCallback === origCB;
|
||||
});
|
||||
} else {
|
||||
listenersToRemove = Object.values(this.listeners);
|
||||
}
|
||||
// Remove the listeners from the reference to prevent memory leaks
|
||||
listenersToRemove.forEach((listener) => {
|
||||
delete this.listeners[listener.listenerId];
|
||||
});
|
||||
return this.database.off(this.refId, listenersToRemove, Object.keys(this.listeners).length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +253,7 @@ export default class Reference extends ReferenceBase {
|
||||
*/
|
||||
orderBy(name: string, key?: string): Reference {
|
||||
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
|
||||
newRef.query.setOrderBy(name, key);
|
||||
newRef.query.orderBy(name, key);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
@ -261,7 +287,7 @@ export default class Reference extends ReferenceBase {
|
||||
*/
|
||||
limit(name: string, limit: number): Reference {
|
||||
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
|
||||
newRef.query.setLimit(name, limit);
|
||||
newRef.query.limit(name, limit);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
@ -308,7 +334,7 @@ export default class Reference extends ReferenceBase {
|
||||
*/
|
||||
filter(name: string, value: any, key?: string): Reference {
|
||||
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
|
||||
newRef.query.setFilter(name, value, key);
|
||||
newRef.query.filter(name, value, key);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,9 @@ import sinon from 'sinon';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
|
||||
function offTests({ describe, it, xcontext, context, firebase }) {
|
||||
describe('ref().off()', () => {
|
||||
xit('doesn\'t unbind children callbacks', async () => {
|
||||
it('doesn\'t unbind children callbacks', async () => {
|
||||
// Setup
|
||||
|
||||
const parentCallback = sinon.spy();
|
||||
@ -33,7 +32,8 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
childCallback.should.be.calledOnce();
|
||||
|
||||
// Returns nothing
|
||||
should(parentRef.off(), undefined);
|
||||
const resp = await parentRef.off();
|
||||
should(resp, undefined);
|
||||
|
||||
// Trigger event parent callback is listening to
|
||||
await parentRef.set(DatabaseContents.DEFAULT);
|
||||
@ -49,7 +49,7 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
childCallback.should.be.calledOnce();
|
||||
|
||||
// Teardown
|
||||
childRef.off();
|
||||
await childRef.off();
|
||||
});
|
||||
|
||||
context('when passed no arguments', () => {
|
||||
@ -71,15 +71,15 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
const arrayLength = DatabaseContents.DEFAULT.array.length;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
valueCallback();
|
||||
ref.on('child_added', () => {
|
||||
childAddedCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('child_added', () => {
|
||||
childAddedCallback();
|
||||
ref.on('value', () => {
|
||||
valueCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@ -89,10 +89,14 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
|
||||
// Check childAddedCallback is really attached
|
||||
await ref.push(DatabaseContents.DEFAULT.number);
|
||||
// Android Note: There is definitely a single listener, but value is called three times
|
||||
// rather than the two you'd perhaps expect
|
||||
valueCallback.should.be.callCount(3);
|
||||
childAddedCallback.should.be.callCount(arrayLength + 1);
|
||||
|
||||
// Returns nothing
|
||||
should(ref.off(), undefined);
|
||||
const resp = await ref.off();
|
||||
should(resp, undefined);
|
||||
|
||||
// Trigger both callbacks
|
||||
|
||||
@ -100,7 +104,7 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
await ref.push(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callbacks should have been unbound and not called again
|
||||
valueCallback.should.be.calledOnce();
|
||||
valueCallback.should.be.callCount(3);
|
||||
childAddedCallback.should.be.callCount(arrayLength + 1);
|
||||
});
|
||||
});
|
||||
@ -122,7 +126,7 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
});
|
||||
});
|
||||
|
||||
xit('detaches all callbacks listening for that event', async () => {
|
||||
it('detaches all callbacks listening for that event', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
@ -148,7 +152,8 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
// Returns nothing
|
||||
should(ref.off('value'), undefined);
|
||||
const resp = await ref.off('value');
|
||||
should(resp, undefined);
|
||||
|
||||
// Assertions
|
||||
|
||||
@ -169,91 +174,110 @@ function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
});
|
||||
});
|
||||
|
||||
xit('detaches only that callback', async () => {
|
||||
it('detaches only that callback', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
let callbackA;
|
||||
let callbackB;
|
||||
const spyA = sinon.spy();
|
||||
const spyB = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
// Attach the callback the first time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
callbackA = () => {
|
||||
spyA();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
ref.on('value', callbackA);
|
||||
});
|
||||
|
||||
// Attach the callback the second time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackB();
|
||||
callbackB = () => {
|
||||
spyB();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
ref.on('value', callbackB);
|
||||
});
|
||||
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledOnce();
|
||||
spyA.should.be.calledOnce();
|
||||
spyB.should.be.calledOnce();
|
||||
|
||||
// Detach callbackA, only
|
||||
should(ref.off('value', callbackA), undefined);
|
||||
const resp = await ref.off('value', callbackA);
|
||||
should(resp, undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.string);
|
||||
await ref.set(DatabaseContents.NEW.string);
|
||||
|
||||
// Add a delay to ensure that the .set() has had time to be registered
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// CallbackB should still be attached
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledTwice();
|
||||
spyA.should.be.calledOnce();
|
||||
spyB.should.be.calledTwice();
|
||||
|
||||
// Teardown
|
||||
should(ref.off('value', callbackB), undefined);
|
||||
});
|
||||
|
||||
context('that has been added multiple times', () => {
|
||||
xit('must be called as many times completely remove', async () => {
|
||||
it('must be called as many times completely remove', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const spyA = sinon.spy();
|
||||
let callbackA;
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
// Attach the callback the first time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
callbackA = () => {
|
||||
spyA();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
ref.on('value', callbackA);
|
||||
});
|
||||
|
||||
// Attach the callback the second time
|
||||
ref.on('value', callbackA);
|
||||
|
||||
// Add a delay to ensure that the .on() has had time to be registered
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
spyA.should.be.calledTwice();
|
||||
|
||||
// Undo the first time the callback was attached
|
||||
should(ref.off(), undefined);
|
||||
const resp = await ref.off('value', callbackA);
|
||||
should(resp, undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callback should have been called only once because one of the attachments
|
||||
// has been removed
|
||||
callbackA.should.be.calledThrice();
|
||||
// Android Note: There is definitely a single listener, but value is called twice
|
||||
// rather than the once you'd perhaps expect
|
||||
spyA.should.be.callCount(4);
|
||||
|
||||
// Undo the second attachment
|
||||
should(ref.off(), undefined);
|
||||
const resp2 = await ref.off('value', callbackA);
|
||||
should(resp2, undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callback should not have been called any more times
|
||||
callbackA.should.be.calledThrice();
|
||||
spyA.should.be.callCount(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -12,7 +12,7 @@ function rootTests({ describe, it, context, firebase }) {
|
||||
|
||||
// Assertion
|
||||
|
||||
nonRootRef.root.should.eql(rootRef);
|
||||
nonRootRef.root.query.should.eql(rootRef.query);
|
||||
});
|
||||
});
|
||||
|
||||
@ -27,7 +27,7 @@ function rootTests({ describe, it, context, firebase }) {
|
||||
|
||||
// Assertion
|
||||
|
||||
rootRef.root.should.eql(rootRef);
|
||||
rootRef.root.query.should.eql(rootRef.query);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user