[js][android] database sync tree implementation with adjusted tests
This commit is contained in:
parent
67985f8e90
commit
e4d27029b9
|
@ -77,20 +77,21 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name
|
|
||||||
* @param path
|
|
||||||
* @param dataSnapshot
|
* @param dataSnapshot
|
||||||
* @param refId
|
* @param registration
|
||||||
|
* @param previousChildName
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId) {
|
public static WritableMap snapshotToMap(DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||||
|
WritableMap result = Arguments.createMap();
|
||||||
WritableMap snapshot = Arguments.createMap();
|
WritableMap snapshot = Arguments.createMap();
|
||||||
WritableMap eventMap = Arguments.createMap();
|
|
||||||
|
|
||||||
snapshot.putString("key", dataSnapshot.getKey());
|
snapshot.putString("key", dataSnapshot.getKey());
|
||||||
snapshot.putBoolean("exists", dataSnapshot.exists());
|
snapshot.putBoolean("exists", dataSnapshot.exists());
|
||||||
snapshot.putBoolean("hasChildren", dataSnapshot.hasChildren());
|
snapshot.putBoolean("hasChildren", dataSnapshot.hasChildren());
|
||||||
snapshot.putDouble("childrenCount", dataSnapshot.getChildrenCount());
|
snapshot.putDouble("childrenCount", dataSnapshot.getChildrenCount());
|
||||||
|
snapshot.putArray("childKeys", Utils.getChildKeys(dataSnapshot));
|
||||||
|
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
|
||||||
|
|
||||||
if (!dataSnapshot.hasChildren()) {
|
if (!dataSnapshot.hasChildren()) {
|
||||||
mapPutValue("value", dataSnapshot.getValue(), snapshot);
|
mapPutValue("value", dataSnapshot.getValue(), snapshot);
|
||||||
|
@ -103,16 +104,50 @@ public class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot.putArray("childKeys", Utils.getChildKeys(dataSnapshot));
|
|
||||||
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
|
|
||||||
|
|
||||||
eventMap.putInt("refId", refId);
|
result.putMap("snapshot", snapshot);
|
||||||
eventMap.putString("path", path);
|
result.putString("previousChildName", previousChildName);
|
||||||
eventMap.putMap("snapshot", snapshot);
|
return result;
|
||||||
eventMap.putString("eventName", name);
|
}
|
||||||
eventMap.putString("previousChildName", previousChildName);
|
|
||||||
|
|
||||||
return eventMap;
|
/**
|
||||||
|
*
|
||||||
|
* @param map
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static WritableMap readableMapToWritableMap(ReadableMap map) {
|
||||||
|
WritableMap writableMap = Arguments.createMap();
|
||||||
|
|
||||||
|
ReadableMapKeySetIterator iterator = map.keySetIterator();
|
||||||
|
while (iterator.hasNextKey()) {
|
||||||
|
String key = iterator.nextKey();
|
||||||
|
ReadableType type = map.getType(key);
|
||||||
|
switch (type) {
|
||||||
|
case Null:
|
||||||
|
writableMap.putNull(key);
|
||||||
|
break;
|
||||||
|
case Boolean:
|
||||||
|
writableMap.putBoolean(key, map.getBoolean(key));
|
||||||
|
break;
|
||||||
|
case Number:
|
||||||
|
writableMap.putDouble(key, map.getDouble(key));
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
writableMap.putString(key, map.getString(key));
|
||||||
|
break;
|
||||||
|
case Map:
|
||||||
|
writableMap.putMap(key, readableMapToWritableMap(map.getMap(key)));
|
||||||
|
break;
|
||||||
|
case Array:
|
||||||
|
// TODO writableMap.putArray(key, readableArrayToWritableArray(map.getArray(key)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return writableMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,7 +35,7 @@ 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, ChildEventListener> childEventListeners;
|
||||||
private HashMap<String, ValueEventListener> valueEventListeners;
|
private HashMap<String, ValueEventListener> valueEventListeners;
|
||||||
private SparseArray<RNFirebaseDatabaseReference> references = new SparseArray<>();
|
private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<String, RNFirebaseDatabaseReference>();
|
||||||
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||||
|
|
||||||
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||||
|
@ -103,10 +103,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
* @param state
|
* @param state
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void keepSynced(String appName, int id, String path, ReadableArray modifiers, Boolean state) {
|
public void keepSynced(String appName, String key, String path, ReadableArray modifiers, Boolean state) {
|
||||||
getInternalReferenceForApp(appName, id, path, modifiers, false)
|
getInternalReferenceForApp(appName, key, path, modifiers).getQuery().keepSynced(state);
|
||||||
.getQuery()
|
|
||||||
.keepSynced(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -410,34 +408,36 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
* Subscribe once to a firebase reference.
|
* Subscribe once to a firebase reference.
|
||||||
*
|
*
|
||||||
* @param appName
|
* @param appName
|
||||||
* @param refId
|
* @param key
|
||||||
* @param path
|
* @param path
|
||||||
* @param modifiers
|
* @param modifiers
|
||||||
* @param eventName
|
* @param eventName
|
||||||
* @param promise
|
* @param promise
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void once(String appName, int refId, String path, ReadableArray modifiers, String eventName, Promise promise) {
|
public void once(String appName, String key, String path, ReadableArray modifiers, String eventName, Promise promise) {
|
||||||
getInternalReferenceForApp(appName, refId, path, modifiers, false).once(eventName, promise);
|
getInternalReferenceForApp(appName, key, path, modifiers).once(eventName, promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to real time events for the specified database path + modifiers
|
* Subscribe to real time events for the specified database path + modifiers
|
||||||
*
|
*
|
||||||
* @param appName String
|
* @param appName String
|
||||||
* @param args ReadableMap
|
* @param props ReadableMap
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void on(String appName, ReadableMap args) {
|
public void on(String appName, ReadableMap props) {
|
||||||
RNFirebaseDatabaseReference ref = getInternalReferenceForApp(
|
getInternalReferenceForApp(appName, props).on(
|
||||||
appName,
|
this,
|
||||||
args.getInt("id"),
|
props.getString("eventType"),
|
||||||
args.getString("path"),
|
props.getMap("registration")
|
||||||
args.getArray("modifiers"),
|
|
||||||
true
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void off(String appName, ReadableMap args) {
|
||||||
|
|
||||||
ref.on(this, args.getString("eventType"), args.getString("eventQueryKey"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -511,25 +511,49 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
* Return an existing or create a new RNFirebaseDatabaseReference instance.
|
* Return an existing or create a new RNFirebaseDatabaseReference instance.
|
||||||
*
|
*
|
||||||
* @param appName
|
* @param appName
|
||||||
* @param refId
|
* @param key
|
||||||
* @param path
|
* @param path
|
||||||
* @param modifiers
|
* @param modifiers
|
||||||
* @param keep
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, int refId, String path, ReadableArray modifiers, Boolean keep) {
|
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, String key, String path, ReadableArray modifiers) {
|
||||||
RNFirebaseDatabaseReference existingRef = references.get(refId);
|
RNFirebaseDatabaseReference existingRef = references.get(key);
|
||||||
|
|
||||||
if (existingRef == null) {
|
if (existingRef == null) {
|
||||||
existingRef = new RNFirebaseDatabaseReference(
|
existingRef = new RNFirebaseDatabaseReference(
|
||||||
getReactApplicationContext(),
|
getReactApplicationContext(),
|
||||||
appName,
|
appName,
|
||||||
refId,
|
key,
|
||||||
|
path,
|
||||||
|
modifiers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param props
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, ReadableMap props) {
|
||||||
|
String key = props.getString("key");
|
||||||
|
String path = props.getString("path");
|
||||||
|
ReadableArray modifiers = props.getArray("modifiers");
|
||||||
|
|
||||||
|
RNFirebaseDatabaseReference existingRef = references.get(key);
|
||||||
|
|
||||||
|
if (existingRef == null) {
|
||||||
|
existingRef = new RNFirebaseDatabaseReference(
|
||||||
|
getReactApplicationContext(),
|
||||||
|
appName,
|
||||||
|
key,
|
||||||
path,
|
path,
|
||||||
modifiers
|
modifiers
|
||||||
);
|
);
|
||||||
|
|
||||||
if (keep) references.put(refId, existingRef);
|
references.put(key, existingRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
return existingRef;
|
return existingRef;
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
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;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.SparseArray;
|
|
||||||
|
|
||||||
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.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
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;
|
||||||
|
@ -24,10 +22,10 @@ import com.google.firebase.database.ValueEventListener;
|
||||||
|
|
||||||
import io.invertase.firebase.Utils;
|
import io.invertase.firebase.Utils;
|
||||||
|
|
||||||
public class RNFirebaseDatabaseReference {
|
class RNFirebaseDatabaseReference {
|
||||||
private static final String TAG = "RNFirebaseDBReference";
|
private static final String TAG = "RNFirebaseDBReference";
|
||||||
|
|
||||||
private int refId;
|
private String key;
|
||||||
private Query query;
|
private Query query;
|
||||||
private String path;
|
private String path;
|
||||||
private String appName;
|
private String appName;
|
||||||
|
@ -41,12 +39,12 @@ public class RNFirebaseDatabaseReference {
|
||||||
/**
|
/**
|
||||||
* @param context
|
* @param context
|
||||||
* @param app
|
* @param app
|
||||||
* @param id
|
* @param refKey
|
||||||
* @param refPath
|
* @param refPath
|
||||||
* @param modifiersArray
|
* @param modifiersArray
|
||||||
*/
|
*/
|
||||||
RNFirebaseDatabaseReference(ReactContext context, String app, int id, String refPath, ReadableArray modifiersArray) {
|
RNFirebaseDatabaseReference(ReactContext context, String app, String refKey, String refPath, ReadableArray modifiersArray) {
|
||||||
refId = id;
|
key = refKey;
|
||||||
appName = app;
|
appName = app;
|
||||||
path = refPath;
|
path = refPath;
|
||||||
reactContext = context;
|
reactContext = context;
|
||||||
|
@ -62,7 +60,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);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, null);
|
||||||
promise.resolve(data);
|
promise.resolve(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +72,7 @@ public class RNFirebaseDatabaseReference {
|
||||||
|
|
||||||
query.addListenerForSingleValueEvent(onceValueEventListener);
|
query.addListenerForSingleValueEvent(onceValueEventListener);
|
||||||
|
|
||||||
Log.d(TAG, "Added OnceValueEventListener for refId: " + refId);
|
Log.d(TAG, "Added OnceValueEventListener for key: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +87,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);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, previousChildName);
|
||||||
promise.resolve(data);
|
promise.resolve(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +96,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);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, previousChildName);
|
||||||
promise.resolve(data);
|
promise.resolve(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +105,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);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, null);
|
||||||
promise.resolve(data);
|
promise.resolve(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +114,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);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, previousChildName);
|
||||||
promise.resolve(data);
|
promise.resolve(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,13 +133,14 @@ 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 database
|
||||||
|
* @param registration
|
||||||
*/
|
*/
|
||||||
void on(RNFirebaseDatabase database, String eventName, String queryKey) {
|
void on(RNFirebaseDatabase database, String eventType, ReadableMap registration) {
|
||||||
if (eventName.equals("value")) {
|
if (eventType.equals("value")) {
|
||||||
addValueEventListener(queryKey, database);
|
addValueEventListener(registration, database);
|
||||||
} else {
|
} else {
|
||||||
addChildEventListener(queryKey, database, eventName);
|
addChildEventListener(registration, eventType, database);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,94 +160,100 @@ public class RNFirebaseDatabaseReference {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param queryKey
|
* @param registration
|
||||||
* @param eventName
|
* @param eventType
|
||||||
|
* @param database
|
||||||
*/
|
*/
|
||||||
private void addChildEventListener(final String queryKey, final RNFirebaseDatabase database, final String eventName) {
|
private void addChildEventListener(final ReadableMap registration, final String eventType, final RNFirebaseDatabase database) {
|
||||||
if (!database.hasChildEventListener(queryKey)) {
|
final String eventRegistrationKey = registration.getString("eventRegistrationKey");
|
||||||
|
final String registrationCancellationKey = registration.getString("registrationCancellationKey");
|
||||||
|
|
||||||
|
if (!database.hasChildEventListener(eventRegistrationKey)) {
|
||||||
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(eventType)) {
|
||||||
handleDatabaseEvent("child_added", queryKey, dataSnapshot, previousChildName);
|
handleDatabaseEvent("child_added", registration, 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(eventType)) {
|
||||||
handleDatabaseEvent("child_changed", queryKey, dataSnapshot, previousChildName);
|
handleDatabaseEvent("child_changed", registration, dataSnapshot, previousChildName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||||
if ("child_removed".equals(eventName)) {
|
if ("child_removed".equals(eventType)) {
|
||||||
handleDatabaseEvent("child_removed", queryKey, dataSnapshot, null);
|
handleDatabaseEvent("child_removed", registration, 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(eventType)) {
|
||||||
handleDatabaseEvent("child_moved", queryKey, dataSnapshot, previousChildName);
|
handleDatabaseEvent("child_moved", registration, dataSnapshot, previousChildName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelled(DatabaseError error) {
|
public void onCancelled(DatabaseError error) {
|
||||||
query.removeEventListener(this);
|
query.removeEventListener(this);
|
||||||
database.removeChildEventListener(queryKey);
|
database.removeChildEventListener(eventRegistrationKey);
|
||||||
handleDatabaseError(queryKey, error);
|
handleDatabaseError(registration, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
database.addChildEventListener(queryKey, childEventListener);
|
database.addChildEventListener(eventRegistrationKey, childEventListener);
|
||||||
query.addChildEventListener(childEventListener);
|
query.addChildEventListener(childEventListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param queryKey
|
* @param registration
|
||||||
*/
|
*/
|
||||||
private void addValueEventListener(final String queryKey, final RNFirebaseDatabase database) {
|
private void addValueEventListener(final ReadableMap registration, final RNFirebaseDatabase database) {
|
||||||
if (!database.hasValueEventListener(queryKey)) {
|
final String eventRegistrationKey = registration.getString("eventRegistrationKey");
|
||||||
|
final String registrationCancellationKey = registration.getString("registrationCancellationKey");
|
||||||
|
|
||||||
|
if (!database.hasValueEventListener(eventRegistrationKey)) {
|
||||||
ValueEventListener valueEventListener = new ValueEventListener() {
|
ValueEventListener valueEventListener = new ValueEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||||
handleDatabaseEvent("value", queryKey, dataSnapshot, null);
|
handleDatabaseEvent("value", registration, dataSnapshot, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelled(DatabaseError error) {
|
public void onCancelled(DatabaseError error) {
|
||||||
query.removeEventListener(this);
|
query.removeEventListener(this);
|
||||||
database.removeValueEventListener(queryKey);
|
database.removeValueEventListener(eventRegistrationKey);
|
||||||
handleDatabaseError(queryKey, error);
|
handleDatabaseError(registration, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
database.addValueEventListener(queryKey, valueEventListener);
|
database.addValueEventListener(eventRegistrationKey, valueEventListener);
|
||||||
query.addValueEventListener(valueEventListener);
|
query.addValueEventListener(valueEventListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name
|
* @param eventType
|
||||||
* @param dataSnapshot
|
* @param dataSnapshot
|
||||||
* @param previousChildName
|
* @param previousChildName
|
||||||
*/
|
*/
|
||||||
private void handleDatabaseEvent(final String name, String queryKey, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
private void handleDatabaseEvent(String eventType, ReadableMap registration, DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||||
WritableMap evt = Arguments.createMap();
|
WritableMap event = Arguments.createMap();
|
||||||
WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId);
|
WritableMap data = Utils.snapshotToMap(dataSnapshot, previousChildName);
|
||||||
|
|
||||||
evt.putMap("body", data);
|
event.putMap("data", data);
|
||||||
evt.putInt("refId", refId);
|
event.putString("key", key);
|
||||||
evt.putString("eventName", name);
|
event.putString("eventType", eventType);
|
||||||
evt.putString("appName", appName);
|
event.putMap("registration", Utils.readableMapToWritableMap(registration));
|
||||||
evt.putString("queryKey", queryKey);
|
|
||||||
|
|
||||||
Utils.sendEvent(reactContext, "database_on_event", evt);
|
Utils.sendEvent(reactContext, "database_sync_event", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -256,13 +261,14 @@ public class RNFirebaseDatabaseReference {
|
||||||
*
|
*
|
||||||
* @param error
|
* @param error
|
||||||
*/
|
*/
|
||||||
private void handleDatabaseError(String queryKey, DatabaseError error) {
|
private void handleDatabaseError(ReadableMap registration, DatabaseError error) {
|
||||||
WritableMap errMap = RNFirebaseDatabase.getJSError(error);
|
WritableMap event = Arguments.createMap();
|
||||||
errMap.putInt("refId", refId);
|
|
||||||
errMap.putString("path", path);
|
event.putString("key", key);
|
||||||
errMap.putString("appName", appName);
|
event.putMap("error", RNFirebaseDatabase.getJSError(error));
|
||||||
errMap.putString("queryKey", queryKey);
|
event.putMap("registration", Utils.readableMapToWritableMap(registration));
|
||||||
Utils.sendEvent(reactContext, "database_cancel_event", errMap);
|
|
||||||
|
Utils.sendEvent(reactContext, "database_sync_event", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ export default class FirebaseApp {
|
||||||
Object.assign(getInstance, statics, {
|
Object.assign(getInstance, statics, {
|
||||||
nativeModuleExists: !!NativeModules[`RNFirebase${capitalizeFirstLetter(name)}`],
|
nativeModuleExists: !!NativeModules[`RNFirebase${capitalizeFirstLetter(name)}`],
|
||||||
});
|
});
|
||||||
|
|
||||||
return getInstance;
|
return getInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
import { Platform } from 'react-native';
|
import { Platform, NativeModules } from 'react-native';
|
||||||
|
|
||||||
const DEFAULT_APP_NAME = Platform.OS === 'ios' ? '__FIRAPP_DEFAULT' : '[DEFAULT]';
|
const DEFAULT_APP_NAME = Platform.OS === 'ios' ? '__FIRAPP_DEFAULT' : '[DEFAULT]';
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
SharedEventEmitter: new EventEmitter(),
|
SharedEventEmitter: new EventEmitter(),
|
||||||
|
SyncTree: NativeModules.RNFirebaseDatabase ? new SyncTree(NativeModules.RNFirebaseDatabase) : null,
|
||||||
|
|
||||||
// internal utils
|
// internal utils
|
||||||
deleteApp(name: String) {
|
deleteApp(name: String) {
|
||||||
|
|
|
@ -5,10 +5,8 @@
|
||||||
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';
|
||||||
import { nativeToJSError } from './../../utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Database
|
* @class Database
|
||||||
|
@ -16,8 +14,6 @@ import { nativeToJSError } from './../../utils';
|
||||||
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._transactionHandler = new TransactionHandler(this);
|
this._transactionHandler = new TransactionHandler(this);
|
||||||
|
|
||||||
if (this._options.persistence) {
|
if (this._options.persistence) {
|
||||||
|
@ -25,68 +21,7 @@ export default class Database extends ModuleBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
this._serverTimeOffset = 0; // TODO ----^
|
||||||
this.addListener(
|
|
||||||
this._getAppEventName('database_cancel_event'),
|
|
||||||
this._handleCancelEvent.bind(this),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addListener(
|
|
||||||
this._getAppEventName('database_on_event'),
|
|
||||||
this._handleOnEvent.bind(this),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes native database 'on' events to their js equivalent counterpart.
|
|
||||||
* If there is no longer any listeners remaining for this event we internally
|
|
||||||
* call the native unsub method to prevent further events coming through.
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleOnEvent(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes native database query listener cancellation events to their js counterparts.
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleCancelEvent(event) {
|
|
||||||
const { queryKey, code, message, path, refId, appName } = event;
|
|
||||||
const remainingListeners = this.listeners(`${queryKey}:cancelled`);
|
|
||||||
|
|
||||||
if (remainingListeners && remainingListeners.length) {
|
|
||||||
const error = nativeToJSError(code, message, { path, queryKey, refId, appName });
|
|
||||||
this.emit(`${queryKey}:cancelled`, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
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 {
|
import {
|
||||||
promiseOrCallback,
|
promiseOrCallback,
|
||||||
isFunction,
|
isFunction,
|
||||||
|
@ -17,8 +17,9 @@ import {
|
||||||
generatePushID,
|
generatePushID,
|
||||||
} from './../../utils';
|
} from './../../utils';
|
||||||
|
|
||||||
// Unique Reference ID for native events
|
import INTERNALS from './../../internals';
|
||||||
let refId = 1;
|
|
||||||
|
// track all event registrations by path
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for event types
|
* Enum for event types
|
||||||
|
@ -62,7 +63,6 @@ const ReferenceEventTypes = {
|
||||||
*/
|
*/
|
||||||
export default class Reference extends ReferenceBase {
|
export default class Reference extends ReferenceBase {
|
||||||
|
|
||||||
_refId: number;
|
|
||||||
_refListeners: { [listenerId: number]: DatabaseListener };
|
_refListeners: { [listenerId: number]: DatabaseListener };
|
||||||
_database: Object;
|
_database: Object;
|
||||||
_query: Query;
|
_query: Query;
|
||||||
|
@ -70,13 +70,12 @@ export default class Reference extends ReferenceBase {
|
||||||
constructor(database: Object, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
constructor(database: Object, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
||||||
super(path, database);
|
super(path, database);
|
||||||
this._promise = null;
|
this._promise = null;
|
||||||
this._refId = refId++;
|
|
||||||
this._listeners = 0;
|
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);
|
||||||
this.log = this._database.log;
|
this.log = this._database.log;
|
||||||
this.log.debug('Created new Reference', this._refId, this.path);
|
this.log.debug('Created new Reference', this._getRefKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +89,7 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
keepSynced(bool: boolean) {
|
keepSynced(bool: boolean) {
|
||||||
return this._database._native.keepSynced(this._refId, this.path, this._query.getModifiers(), bool);
|
return this._database._native.keepSynced(this._getRefKey(), this.path, this._query.getModifiers(), bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -225,7 +224,7 @@ export default class Reference extends ReferenceBase {
|
||||||
cancelOrContext: (error: FirebaseError) => void,
|
cancelOrContext: (error: FirebaseError) => void,
|
||||||
context?: Object,
|
context?: Object,
|
||||||
) {
|
) {
|
||||||
return this._database._native.once(this._refId, this.path, this._query.getModifiers(), eventName)
|
return this._database._native.once(this._getRefKey(), this.path, this._query.getModifiers(), eventName)
|
||||||
.then(({ snapshot }) => {
|
.then(({ snapshot }) => {
|
||||||
const _snapshot = new Snapshot(this, snapshot);
|
const _snapshot = new Snapshot(this, snapshot);
|
||||||
|
|
||||||
|
@ -513,11 +512,23 @@ export default class Reference extends ReferenceBase {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unique key based on this refs path and query modifiers
|
* Generate a unique registration key.
|
||||||
|
*
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
makeQueryKey() {
|
_getRegistrationKey(eventType) {
|
||||||
return `$${this.path}$${this._query.queryIdentifier()}$${this._refId}$${this._listeners}`;
|
return `$${this._database._appName}$${this.path}$${this._query.queryIdentifier()}$${this._listeners}$${eventType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a string that uniquely identifies this
|
||||||
|
* combination of path and query modifiers
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getRefKey() {
|
||||||
|
return `$${this._database._appName}$${this.path}$${this._query.queryIdentifier()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -571,17 +582,32 @@ export default class Reference extends ReferenceBase {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Register a listener for data changes at the current ref's location.
|
||||||
|
* The primary method of reading data from a Database.
|
||||||
*
|
*
|
||||||
* @param eventType
|
* Listeners can be unbound using {@link off}.
|
||||||
* @param callback
|
*
|
||||||
* @param cancelCallbackOrContext
|
* Event Types:
|
||||||
* @param context
|
*
|
||||||
* @return {*}
|
* - value: {@link callback}.
|
||||||
|
* - child_added: {@link callback}
|
||||||
|
* - child_removed: {@link callback}
|
||||||
|
* - child_changed: {@link callback}
|
||||||
|
* - child_moved: {@link callback}
|
||||||
|
*
|
||||||
|
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
|
||||||
|
* @param {ReferenceEventCallback} callback - Function that will be called
|
||||||
|
* when the event occurs with the new data.
|
||||||
|
* @param {cancelCallbackOrContext=} cancelCallbackOrContext - Optional callback that is called
|
||||||
|
* if the event subscription fails. {@link cancelCallbackOrContext}
|
||||||
|
* @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}
|
||||||
*/
|
*/
|
||||||
// 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
|
|
||||||
on(eventType: string, callback: () => any, cancelCallbackOrContext?: () => any, context?: Object): Function {
|
on(eventType: string, callback: () => any, cancelCallbackOrContext?: () => any, context?: Object): Function {
|
||||||
if (!eventType) {
|
if (!eventType) {
|
||||||
throw new Error('Query.on failed: Function called with 0 arguments. Expects at least 2.');
|
throw new Error('Query.on failed: Function called with 0 arguments. Expects at least 2.');
|
||||||
|
@ -607,38 +633,88 @@ export default class Reference extends ReferenceBase {
|
||||||
throw new Error('Query.on failed: Function called with 4 arguments, but third optional argument `cancelCallbackOrContext` was not a function.');
|
throw new Error('Query.on failed: Function called with 4 arguments, but third optional argument `cancelCallbackOrContext` was not a function.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
|
const eventRegistrationKey = this._getRegistrationKey(eventType);
|
||||||
|
const registrationCancellationKey = `${eventRegistrationKey}$cancelled`;
|
||||||
const _context = (cancelCallbackOrContext && !isFunction(cancelCallbackOrContext)) ? cancelCallbackOrContext : context;
|
const _context = (cancelCallbackOrContext && !isFunction(cancelCallbackOrContext)) ? cancelCallbackOrContext : context;
|
||||||
|
|
||||||
INTERNALS.SharedEventEmitter.addListener(eventQueryKey, _context ? callback.bind(_context) : callback);
|
this._syncTree.addRegistration(
|
||||||
|
{
|
||||||
|
eventType,
|
||||||
|
ref: this,
|
||||||
|
path: this.path,
|
||||||
|
key: this._getRefKey(),
|
||||||
|
appName: this._database._appName,
|
||||||
|
registration: eventRegistrationKey,
|
||||||
|
},
|
||||||
|
_context ? callback.bind(_context) : callback,
|
||||||
|
);
|
||||||
|
|
||||||
if (isFunction(cancelCallbackOrContext)) {
|
if (isFunction(cancelCallbackOrContext)) {
|
||||||
INTERNALS.SharedEventEmitter.once(`${eventQueryKey}:cancelled`, _context ? cancelCallbackOrContext.bind(_context) : cancelCallbackOrContext);
|
// cancellations have their own separate registration
|
||||||
|
// as these are one off events, and they're not guaranteed
|
||||||
|
// to occur either, only happens on failure to register on native
|
||||||
|
this._syncTree.addRegistration(
|
||||||
|
{
|
||||||
|
ref: this,
|
||||||
|
once: true,
|
||||||
|
path: this.path,
|
||||||
|
key: this._getRefKey(),
|
||||||
|
appName: this._database._appName,
|
||||||
|
eventType: `${eventType}$cancelled`,
|
||||||
|
registration: registrationCancellationKey,
|
||||||
|
},
|
||||||
|
_context ? cancelCallbackOrContext.bind(_context) : cancelCallbackOrContext,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialise the native listener if not already listening
|
// initialise the native listener if not already listening
|
||||||
this._database._native.on({
|
this._database._native.on({
|
||||||
eventType,
|
eventType,
|
||||||
eventQueryKey,
|
|
||||||
id: this._refId, // todo remove
|
|
||||||
path: this.path,
|
path: this.path,
|
||||||
|
key: this._getRefKey(),
|
||||||
|
appName: this._database._appName,
|
||||||
modifiers: this._query.getModifiers(),
|
modifiers: this._query.getModifiers(),
|
||||||
|
registration: {
|
||||||
|
eventRegistrationKey,
|
||||||
|
registrationCancellationKey,
|
||||||
|
hasCancellationCallback: isFunction(cancelCallbackOrContext),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this._database._references[this._refId]) {
|
// increment number of listeners - just s short way of making
|
||||||
this._database._references[this._refId] = this;
|
// every registration unique per .on() call
|
||||||
}
|
|
||||||
|
|
||||||
this._listeners = this._listeners + 1;
|
this._listeners = this._listeners + 1;
|
||||||
|
|
||||||
|
// return original unbound successCallback for
|
||||||
|
// the purposes of calling .off(eventType, callback) at a later date
|
||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Detaches a callback previously attached with on().
|
||||||
*
|
*
|
||||||
|
* Detach a callback previously attached with on(). Note that if on() was called
|
||||||
|
* multiple times with the same eventType and callback, the callback will be called
|
||||||
|
* multiple times for each event, and off() must be called multiple times to
|
||||||
|
* remove the callback. Calling off() on a parent listener will not automatically
|
||||||
|
* remove listeners registered on child nodes, off() must also be called on any
|
||||||
|
* child listeners to remove the callback.
|
||||||
|
*
|
||||||
|
* If a callback is not specified, all callbacks for the specified eventType will be removed.
|
||||||
|
* Similarly, if no eventType or callback is specified, all callbacks for the Reference will be removed.
|
||||||
* @param eventType
|
* @param eventType
|
||||||
* @param originalCallback
|
* @param originalCallback
|
||||||
*/
|
*/
|
||||||
off(eventType?: string = '', originalCallback?: () => any) {
|
off(eventType?: string = '', originalCallback?: () => any) {
|
||||||
|
if (!arguments.length) {
|
||||||
|
// Firebase Docs:
|
||||||
|
// if no eventType or callback is specified, all callbacks for the Reference will be removed.
|
||||||
|
return this._syncTree.removeListenersForRegistrations(this._syncTree.getRegistrationsByPath(this.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VALIDATE ARGS
|
||||||
|
*/
|
||||||
if (eventType && (!isString(eventType) || !ReferenceEventTypes[eventType])) {
|
if (eventType && (!isString(eventType) || !ReferenceEventTypes[eventType])) {
|
||||||
throw new Error(`Query.off failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
|
throw new Error(`Query.off failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
|
||||||
}
|
}
|
||||||
|
@ -647,34 +723,36 @@ export default class Reference extends ReferenceBase {
|
||||||
throw new Error('Query.off failed: Function called with 2 arguments, but second optional argument was not a function.');
|
throw new Error('Query.off failed: Function called with 2 arguments, but second optional argument was not a function.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType) {
|
// Firebase Docs:
|
||||||
const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
|
// Note that if on() was called
|
||||||
|
// multiple times with the same eventType and callback, the callback will be called
|
||||||
|
// multiple times for each event, and off() must be called multiple times to
|
||||||
|
// remove the callback.
|
||||||
|
// Remove only a single registration
|
||||||
|
if (eventType && originalCallback) {
|
||||||
|
const registrations = this._syncTree.getRegistrationsByPathEvent(this.path, eventType);
|
||||||
|
|
||||||
if (originalCallback) {
|
// remove the paired cancellation registration if any exist
|
||||||
INTERNALS.SharedEventEmitter.removeListener(eventQueryKey, originalCallback);
|
this._syncTree.removeListenersForRegistrations([`${registrations[0]}$cancelled`]);
|
||||||
} else {
|
|
||||||
INTERNALS.SharedEventEmitter.removeAllListeners(eventQueryKey);
|
// remove only the first registration to match firebase web sdk
|
||||||
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
|
// call multiple times to remove multiple registrations
|
||||||
|
return this._syncTree.removeListenerRegistrations(originalCallback, [registrations[0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there's any listeners remaining in the js thread
|
// Firebase Docs:
|
||||||
// if there's isn't then call the native .off method which
|
// If a callback is not specified, all callbacks for the specified eventType will be removed.
|
||||||
// will unsubscribe from the native firebase listeners
|
const registrations = this._syncTree.getRegistrationsByPathEvent(this.path, eventType);
|
||||||
const remainingListeners = INTERNALS.SharedEventEmitter.listeners(eventQueryKey);
|
|
||||||
|
|
||||||
if (!remainingListeners || !remainingListeners.length) {
|
this._syncTree.removeListenersForRegistrations(
|
||||||
this._database._native.off(
|
this._syncTree.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`),
|
||||||
this._refId, // todo remove
|
|
||||||
eventQueryKey,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove straggling cancellation listeners
|
return this._syncTree.removeListenersForRegistrations(registrations);
|
||||||
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// todo remove all associated event subs if no event type && no orignalCb
|
get _syncTree() {
|
||||||
|
return INTERNALS.SyncTree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ const NATIVE_MODULE_EVENTS = {
|
||||||
'onAuthStateChanged',
|
'onAuthStateChanged',
|
||||||
],
|
],
|
||||||
Database: [
|
Database: [
|
||||||
'database_on_event',
|
// 'database_on_event',
|
||||||
'database_cancel_event',
|
// 'database_cancel_event',
|
||||||
'database_transaction_event',
|
'database_transaction_event',
|
||||||
// 'database_server_offset', // TODO
|
// 'database_server_offset', // TODO
|
||||||
],
|
],
|
||||||
|
@ -47,11 +47,11 @@ export default class ModuleBase {
|
||||||
* @param withEventEmitter
|
* @param withEventEmitter
|
||||||
*/
|
*/
|
||||||
constructor(firebaseApp, options, moduleName, withEventEmitter = false) {
|
constructor(firebaseApp, options, moduleName, withEventEmitter = false) {
|
||||||
this._options = Object.assign({}, DEFAULTS[moduleName] || {}, options);
|
|
||||||
this._module = moduleName;
|
this._module = moduleName;
|
||||||
this._firebaseApp = firebaseApp;
|
this._firebaseApp = firebaseApp;
|
||||||
this._appName = firebaseApp._name;
|
this._appName = firebaseApp._name;
|
||||||
this._namespace = `${this._appName}:${this._module}`;
|
this._namespace = `${this._appName}:${this._module}`;
|
||||||
|
this._options = Object.assign({}, DEFAULTS[moduleName] || {}, options);
|
||||||
|
|
||||||
// check if native module exists as all native
|
// check if native module exists as all native
|
||||||
// modules are now optionally part of build
|
// modules are now optionally part of build
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
import { NativeEventEmitter } from 'react-native';
|
||||||
|
|
||||||
|
import INTERNALS from './../internals';
|
||||||
|
import DatabaseSnapshot from './../modules/database/snapshot';
|
||||||
|
import DatabaseReference from './../modules/database/reference';
|
||||||
|
import { isString, nativeToJSError } from './../utils';
|
||||||
|
|
||||||
|
type Registration = {
|
||||||
|
key: String,
|
||||||
|
path: String,
|
||||||
|
once?: Boolean,
|
||||||
|
appName: String,
|
||||||
|
eventType: String,
|
||||||
|
registration: String,
|
||||||
|
ref: DatabaseReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internally used to manage firebase database realtime event
|
||||||
|
* subscriptions and keep the listeners in sync in js vs native.
|
||||||
|
*/
|
||||||
|
export default class SyncTree {
|
||||||
|
constructor(databaseNative) {
|
||||||
|
this._tree = {};
|
||||||
|
this._reverseLookup = {};
|
||||||
|
this._databaseNative = databaseNative;
|
||||||
|
this._nativeEmitter = new NativeEventEmitter(databaseNative);
|
||||||
|
this._nativeEmitter.addListener(
|
||||||
|
'database_sync_event',
|
||||||
|
this._handleSyncEvent.bind(this),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleSyncEvent(event) {
|
||||||
|
if (event.error) {
|
||||||
|
this._handleErrorEvent(event);
|
||||||
|
} else {
|
||||||
|
this._handleValueEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes native database 'on' events to their js equivalent counterpart.
|
||||||
|
* If there is no longer any listeners remaining for this event we internally
|
||||||
|
* call the native unsub method to prevent further events coming through.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleValueEvent(event) {
|
||||||
|
const { eventRegistrationKey } = event.registration;
|
||||||
|
const registration = this.getRegistration(eventRegistrationKey);
|
||||||
|
|
||||||
|
if (!registration) {
|
||||||
|
// registration previously revoked
|
||||||
|
// notify native that the registration
|
||||||
|
// no longer exists so it can remove
|
||||||
|
// the native listeners
|
||||||
|
return this._databaseNative.off({
|
||||||
|
key: event.key,
|
||||||
|
eventRegistrationKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { snapshot, previousChildName } = event.data;
|
||||||
|
|
||||||
|
// forward on to users .on(successCallback <-- listener
|
||||||
|
return INTERNALS.SharedEventEmitter.emit(
|
||||||
|
eventRegistrationKey,
|
||||||
|
new DatabaseSnapshot(registration.ref, snapshot),
|
||||||
|
previousChildName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes native database query listener cancellation events to their js counterparts.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleErrorEvent(event) {
|
||||||
|
const { code, message } = event.error;
|
||||||
|
const { eventRegistrationKey, registrationCancellationKey } = event.registration;
|
||||||
|
|
||||||
|
const registration = this.getRegistration(registrationCancellationKey);
|
||||||
|
|
||||||
|
if (registration) {
|
||||||
|
// build a new js error - we additionally attach
|
||||||
|
// the ref as a property for easier debugging
|
||||||
|
const error = nativeToJSError(code, message, { ref: registration.ref });
|
||||||
|
|
||||||
|
// forward on to users .on(successCallback, cancellationCallback <-- listener
|
||||||
|
INTERNALS.SharedEventEmitter.emit(registrationCancellationKey, error);
|
||||||
|
|
||||||
|
// remove the paired event registration - if we received a cancellation
|
||||||
|
// event then it's guaranteed that they'll be no further value events
|
||||||
|
this.removeRegistration(eventRegistrationKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns registration information such as appName, ref, path and registration keys.
|
||||||
|
*
|
||||||
|
* @param registration
|
||||||
|
* @return {null}
|
||||||
|
*/
|
||||||
|
getRegistration(registration): Registration | null {
|
||||||
|
return this._reverseLookup[registration] ? Object.assign({}, this._reverseLookup[registration]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all listeners for the specified registration keys.
|
||||||
|
*
|
||||||
|
* @param registrations
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
removeListenersForRegistrations(registrations) {
|
||||||
|
if (isString(registrations)) {
|
||||||
|
this.removeRegistration(registrations);
|
||||||
|
INTERNALS.SharedEventEmitter.removeAllListeners(registrations);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(registrations)) return 0;
|
||||||
|
for (let i = 0, len = registrations.length; i < len; i++) {
|
||||||
|
this.removeRegistration(registrations[i]);
|
||||||
|
INTERNALS.SharedEventEmitter.removeAllListeners(registrations[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return registrations.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a specific listener from the specified registrations.
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
* @param registrations
|
||||||
|
* @return {Array} array of registrations removed
|
||||||
|
*/
|
||||||
|
removeListenerRegistrations(listener, registrations) {
|
||||||
|
if (!Array.isArray(registrations)) return [];
|
||||||
|
const removed = [];
|
||||||
|
|
||||||
|
for (let i = 0, len = registrations.length; i < len; i++) {
|
||||||
|
const registration = registrations[i];
|
||||||
|
const subscriptions = INTERNALS.SharedEventEmitter._subscriber.getSubscriptionsForType(registration);
|
||||||
|
if (subscriptions) {
|
||||||
|
for (let j = 0, l = subscriptions.length; j < l; j++) {
|
||||||
|
const subscription = subscriptions[j];
|
||||||
|
// The subscription may have been removed during this event loop.
|
||||||
|
// its listener matches the listener in method parameters
|
||||||
|
if (subscription && subscription.listener === listener) {
|
||||||
|
subscription.remove();
|
||||||
|
removed.push(registration);
|
||||||
|
this.removeRegistration(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all registration keys for the specified path.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getRegistrationsByPath(path): Array {
|
||||||
|
const out = [];
|
||||||
|
const eventKeys = Object.keys(this._tree[path] || {});
|
||||||
|
|
||||||
|
for (let i = 0, len = eventKeys.length; i < len; i++) {
|
||||||
|
Array.prototype.push.apply(out, Object.keys(this._tree[path][eventKeys[i]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all registration keys for the specified path and eventType.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param eventType
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getRegistrationsByPathEvent(path, eventType): Array {
|
||||||
|
if (!this._tree[path]) return [];
|
||||||
|
if (!this._tree[path][eventType]) return [];
|
||||||
|
|
||||||
|
return Object.keys(this._tree[path][eventType]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new listener.
|
||||||
|
*
|
||||||
|
* @param parameters
|
||||||
|
* @param listener
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
addRegistration(parameters: Registration, listener): String {
|
||||||
|
const { path, eventType, registration, once } = parameters;
|
||||||
|
|
||||||
|
if (!this._tree[path]) this._tree[path] = {};
|
||||||
|
if (!this._tree[path][eventType]) this._tree[path][eventType] = {};
|
||||||
|
|
||||||
|
this._tree[path][eventType][registration] = 0;
|
||||||
|
this._reverseLookup[registration] = Object.assign({}, parameters);
|
||||||
|
|
||||||
|
if (once) INTERNALS.SharedEventEmitter.once(registration, this._onOnceRemoveRegistration(registration, listener));
|
||||||
|
else INTERNALS.SharedEventEmitter.addListener(registration, listener);
|
||||||
|
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a registration, if it's not a `once` registration then instructs native
|
||||||
|
* to also remove the underlying database query listener.
|
||||||
|
*
|
||||||
|
* @param registration
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
removeRegistration(registration: String): Boolean {
|
||||||
|
if (!this._reverseLookup[registration]) return false;
|
||||||
|
const { path, eventType, once } = this._reverseLookup[registration];
|
||||||
|
|
||||||
|
if (!this._tree[path]) {
|
||||||
|
delete this._reverseLookup[registration];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._tree[path][eventType]) {
|
||||||
|
delete this._reverseLookup[registration];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want `once` events to notify native as they're already
|
||||||
|
// automatically unsubscribed on native when the first event is sent
|
||||||
|
const registrationObj = this._reverseLookup[registration];
|
||||||
|
if (registrationObj && !once) {
|
||||||
|
this._databaseNative.off({
|
||||||
|
key: registrationObj.key,
|
||||||
|
eventType: registrationObj.eventType,
|
||||||
|
eventRegistrationKey: registration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this._tree[path][eventType][registration];
|
||||||
|
delete this._reverseLookup[registration];
|
||||||
|
|
||||||
|
return !!registrationObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a `once` listener with a new function that self de-registers.
|
||||||
|
*
|
||||||
|
* @param registration
|
||||||
|
* @param listener
|
||||||
|
* @return {function(...[*])}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onOnceRemoveRegistration(registration, listener) {
|
||||||
|
return (...args) => {
|
||||||
|
this.removeRegistration(registration);
|
||||||
|
listener(...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -231,6 +231,14 @@ export function map(
|
||||||
}, () => cb && cb(result));
|
}, () => cb && cb(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function capitalizeFirstLetter(string: String) {
|
||||||
|
return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// timestamp of last push, used to prevent local collisions if you push twice in one ms.
|
// timestamp of last push, used to prevent local collisions if you push twice in one ms.
|
||||||
let lastPushTime = 0;
|
let lastPushTime = 0;
|
||||||
|
@ -319,6 +327,11 @@ export function nativeWithApp(appName, NativeModule) {
|
||||||
return native;
|
return native;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param object
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
export function objectToUniqueId(object: Object): String {
|
export function objectToUniqueId(object: Object): String {
|
||||||
if (!isObject(object) || object === null) return JSON.stringify(object);
|
if (!isObject(object) || object === null) return JSON.stringify(object);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { View, Text, Button } from 'react-native';
|
||||||
|
|
||||||
|
export default class HomeScreen extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
bgColor: '#cb2600',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
clickMe = () => {
|
||||||
|
if (this.state.bgColor === '#a8139f') {
|
||||||
|
this.setState({ bgColor: '#cb2600' });
|
||||||
|
} else {
|
||||||
|
this.setState({ bgColor: '#a8139f' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={{ backgroundColor: this.state.bgColor }}>
|
||||||
|
<Text style={{ color: '#fff' }}>Hello</Text>
|
||||||
|
<Text style={{ color: '#22ff31' }}>World</Text>
|
||||||
|
<Button title="Change to pink" onPress={this.clickMe} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,8 +26,8 @@ const testGroups = [
|
||||||
pushTests, onTests, onValueTests, onChildAddedTests, onceTests, updateTests,
|
pushTests, onTests, onValueTests, onChildAddedTests, onceTests, updateTests,
|
||||||
removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
|
removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
|
||||||
priorityTests,
|
priorityTests,
|
||||||
onValueTests, onChildAddedTests, // offTests, // TODO remove for now, until i can fix, want to see the others working first
|
onValueTests, onChildAddedTests,
|
||||||
// onTests,
|
offTests,
|
||||||
];
|
];
|
||||||
|
|
||||||
function registerTestSuite(testSuite) {
|
function registerTestSuite(testSuite) {
|
||||||
|
|
|
@ -89,7 +89,13 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
|
|
||||||
// Check childAddedCallback is really attached
|
// Check childAddedCallback is really attached
|
||||||
await ref.push(DatabaseContents.DEFAULT.number);
|
await ref.push(DatabaseContents.DEFAULT.number);
|
||||||
valueCallback.should.be.callCount(2);
|
|
||||||
|
// stinky test fix - it's all async now so it's not returned within same event loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve(), 15);
|
||||||
|
});
|
||||||
|
|
||||||
|
valueCallback.should.be.calledTwice();
|
||||||
childAddedCallback.should.be.callCount(arrayLength + 1);
|
childAddedCallback.should.be.callCount(arrayLength + 1);
|
||||||
|
|
||||||
// Returns nothing
|
// Returns nothing
|
||||||
|
@ -117,10 +123,9 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
context('that is invalid', () => {
|
context('that is invalid', () => {
|
||||||
it('does nothing', () => {
|
it('throws an exception', () => {
|
||||||
const ref = firebase.native.database().ref('tests/types/array');
|
const ref = firebase.native.database().ref('tests/types/array');
|
||||||
|
(() => ref.off('invalid')).should.throw('Query.off failed: First argument must be a valid string event type: "value, child_added, child_removed, child_changed, child_moved"');
|
||||||
should(ref.off('invalid'), undefined);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -225,7 +230,7 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
context('that has been added multiple times', () => {
|
context('that has been added multiple times', () => {
|
||||||
it('must be called as many times completely remove', async () => {
|
it('must be called as many times to completely remove', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
const spyA = sinon.spy();
|
const spyA = sinon.spy();
|
||||||
|
@ -249,7 +254,7 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve();
|
resolve();
|
||||||
}, 1000);
|
}, 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
spyA.should.be.calledTwice();
|
spyA.should.be.calledTwice();
|
||||||
|
@ -261,9 +266,16 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
// Trigger the event the callback is listening to
|
// Trigger the event the callback is listening to
|
||||||
await ref.set(DatabaseContents.DEFAULT.number);
|
await ref.set(DatabaseContents.DEFAULT.number);
|
||||||
|
|
||||||
|
// Add a delay to ensure that the .set() has had time to be registered
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, 15);
|
||||||
|
});
|
||||||
|
|
||||||
// Callback should have been called only once because one of the attachments
|
// Callback should have been called only once because one of the attachments
|
||||||
// has been removed
|
// has been removed
|
||||||
spyA.should.be.callCount(3);
|
spyA.should.be.calledThrice();
|
||||||
|
|
||||||
// Undo the second attachment
|
// Undo the second attachment
|
||||||
const resp2 = await ref.off('value', callbackA);
|
const resp2 = await ref.off('value', callbackA);
|
||||||
|
@ -272,8 +284,15 @@ function offTests({ describe, it, xcontext, context, firebase }) {
|
||||||
// Trigger the event the callback is listening to
|
// Trigger the event the callback is listening to
|
||||||
await ref.set(DatabaseContents.DEFAULT.number);
|
await ref.set(DatabaseContents.DEFAULT.number);
|
||||||
|
|
||||||
|
// Add a delay to ensure that the .set() has had time to be registered
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, 15);
|
||||||
|
});
|
||||||
|
|
||||||
// Callback should not have been called any more times
|
// Callback should not have been called any more times
|
||||||
spyA.should.be.callCount(3);
|
spyA.should.be.calledThrice();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,23 +60,19 @@ function pushTests({ describe, it, firebase }) {
|
||||||
|
|
||||||
return ref.once('value')
|
return ref.once('value')
|
||||||
.then((snapshot) => {
|
.then((snapshot) => {
|
||||||
console.log('first once');
|
|
||||||
originalListValue = snapshot.val();
|
originalListValue = snapshot.val();
|
||||||
return ref.push(valueToAddToList);
|
return ref.push(valueToAddToList);
|
||||||
})
|
})
|
||||||
.then((pushRef) => {
|
.then((pushRef) => {
|
||||||
console.log('after push');
|
|
||||||
newItemRef = pushRef;
|
newItemRef = pushRef;
|
||||||
return newItemRef.once('value');
|
return newItemRef.once('value');
|
||||||
})
|
})
|
||||||
.then((snapshot) => {
|
.then((snapshot) => {
|
||||||
console.log('second once');
|
|
||||||
newItemValue = snapshot.val();
|
newItemValue = snapshot.val();
|
||||||
newItemValue.should.eql(valueToAddToList);
|
newItemValue.should.eql(valueToAddToList);
|
||||||
return firebase.native.database().ref('tests/types/array').once('value');
|
return firebase.native.database().ref('tests/types/array').once('value');
|
||||||
})
|
})
|
||||||
.then((snapshot) => {
|
.then((snapshot) => {
|
||||||
console.log('third once');
|
|
||||||
newListValue = snapshot.val();
|
newListValue = snapshot.val();
|
||||||
const originalListAsObject = originalListValue.reduce((memo, value, index) => {
|
const originalListAsObject = originalListValue.reduce((memo, value, index) => {
|
||||||
memo[index] = value;
|
memo[index] = value;
|
||||||
|
|
Loading…
Reference in New Issue