mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-24 04:24:52 +08:00
[js][android] Support multiple listeners on a single ref
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user