Nick Lockwood 516bf7bd94 Fixed NativeEventListener deregistration
Summary:
The `EmitterSubscription.remove()` method was previously calling `this.subscriber.removeSubscription(this)` directly, bypassing the mechanism in `NativeEventEmitter` that keeps track of the number of subscriptions.

This meant that native event modules (subclasses of `RCTEventEmitter`) would keep sending events even after all the listeners had been removed. This wasn't a huge overhead, since these modules are singletons and only send one message over the bridge per event, regardless of the number of listeners, but it's still undesirable.

This fixes the problem by routing the `EmitterSubscription.remove()` method through the `EventEmitter` so that `NativeEventEmitter` can apply the additional native calls.

I've also improved the architecture so that each `NativeEventEmitter` uses its own `EventEmitter`, but they currently all still share the same `EventSubscriptionVendor` so that legacy code which registers events via `RCTDeviceEventEmitter` still works.

Reviewed By: vjeux

Differential Revision: D3292361

fbshipit-source-id: d60e881d50351523d2112473703bea826641cdef
2016-05-16 04:13:56 -07:00

79 lines
2.7 KiB
JavaScript

/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BugReporting
* @flow
*/
'use strict';
const BugReportingNativeModule = require('NativeModules').BugReporting;
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
import type EmitterSubscription from 'EmitterSubscription';
type ExtraData = { [key: string]: string };
type SourceCallback = () => string;
/**
* A simple class for collecting bug report data. Components can add sources that will be queried when a bug report
* is created via `collectExtraData`. For example, a list component might add a source that provides the list of rows
* that are currently visible on screen. Components should also remember to call `remove()` on the object that is
* returned by `addSource` when they are unmounted.
*/
class BugReporting {
static _sources: Map<string, SourceCallback> = new Map();
static _subscription: ?EmitterSubscription = null;
/**
* `init` is called in `AppRegistry.runApplication`, so you shouldn't have to worry about it.
*/
static init() {
if (!BugReporting._subscription) {
BugReporting._subscription = RCTDeviceEventEmitter
.addListener('collectBugExtraData', BugReporting.collectExtraData, null);
}
}
/**
* Maps a string key to a simple callback that should return a string payload to be attached
* to a bug report. Source callbacks are called when `collectExtraData` is called.
*
* Returns an object to remove the source when the component unmounts.
*
* Conflicts trample with a warning.
*/
static addSource(key: string, callback: SourceCallback): {remove: () => void} {
if (BugReporting._sources.has(key)) {
console.warn(`BugReporting.addSource called multiple times for same key '${key}'`);
}
BugReporting._sources.set(key, callback);
return {remove: () => { BugReporting._sources.delete(key); }};
}
/**
* This can be called from a native bug reporting flow, or from JS code.
*
* If available, this will call `NativeModules.BugReporting.setExtraData(extraData)`
* after collecting `extraData`.
*/
static collectExtraData(): ExtraData {
const extraData: ExtraData = {};
for (const [key, callback] of BugReporting._sources) {
extraData[key] = callback();
}
console.log('BugReporting extraData:', extraData);
BugReportingNativeModule &&
BugReportingNativeModule.setExtraData &&
BugReportingNativeModule.setExtraData(extraData);
return extraData;
}
}
module.exports = BugReporting;