mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
f5502fbda9
Summary: ## Context In native modules implementing an event emitter, we can wait for JS to subscribe to an event before making the subscription to the right native API in the native module. This is only supported on iOS at the moment and we want to support it on Android too, so we can manage resources more efficiently and avoid custom code to do this on Android, like this: https://www.internalfb.com/intern/diffusion/FBS/browse/master/xplat/js/RKJSModules/public/Dating/Profile/ProfileView/ProfileGemstoneProfileView.js?commit=165ad219e6bf&lines=302-304 The way this works now is by creating instances of `NativeEventEmitter`, where we pass a reference to the native module that needs to be notified when there are new subscriptions. We have explicit code to ignore this native modules in all platforms except for iOS: https://www.internalfb.com/intern/diffusion/FBS/browsefile/master/xplat/js/react-native-github/Libraries/EventEmitter/NativeEventEmitter.js?commit=5a1e671453465e844dd851c458cb2467a2db5d03&lines=44-52 ## Changes This removes the check for iOS from `NativeEventEmitter` so we also try to use the native module to notify subscriptions on Android. We have migrated all existing code passing a native module to `NativeEventEmtiter` to only pass it on iOS, so we don't change this behavior in existing code. Any other existing code using this API is most likely fine too. It didn't work before so the expectation is that the native module wouldn't be implemented on Android anyway. Changelog: [Android][Changed] - Modified `NativeEventEmitter` to also use the passed native module to report subscriptions on Android Reviewed By: yungsters Differential Revision: D27500994 fbshipit-source-id: ef82da04020fb08cd0ea4f1cfffd1da6453ab0b9
113 lines
3.4 KiB
JavaScript
113 lines
3.4 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import {
|
|
type EventSubscription,
|
|
type IEventEmitter,
|
|
} from '../vendor/emitter/EventEmitter';
|
|
import Platform from '../Utilities/Platform';
|
|
import RCTDeviceEventEmitter from './RCTDeviceEventEmitter';
|
|
import invariant from 'invariant';
|
|
|
|
interface NativeModule {
|
|
addListener(eventType: string): void;
|
|
removeListeners(count: number): void;
|
|
}
|
|
|
|
export type {EventSubscription};
|
|
|
|
/**
|
|
* `NativeEventEmitter` is intended for use by Native Modules to emit events to
|
|
* JavaScript listeners. If a `NativeModule` is supplied to the constructor, it
|
|
* will be notified (via `addListener` and `removeListeners`) when the listener
|
|
* count changes to manage "native memory".
|
|
*
|
|
* Currently, all native events are fired via a global `RCTDeviceEventEmitter`.
|
|
* This means event names must be globally unique, and it means that call sites
|
|
* can theoretically listen to `RCTDeviceEventEmitter` (although discouraged).
|
|
*/
|
|
export default class NativeEventEmitter<TEventToArgsMap: {...}>
|
|
implements IEventEmitter<TEventToArgsMap> {
|
|
_nativeModule: ?NativeModule;
|
|
|
|
constructor(nativeModule: ?NativeModule) {
|
|
if (Platform.OS === 'ios') {
|
|
invariant(
|
|
nativeModule != null,
|
|
'`new NativeEventEmitter()` requires a non-null argument.',
|
|
);
|
|
}
|
|
this._nativeModule = nativeModule;
|
|
}
|
|
|
|
addListener<TEvent: $Keys<TEventToArgsMap>>(
|
|
eventType: TEvent,
|
|
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => mixed,
|
|
context?: mixed,
|
|
): EventSubscription {
|
|
this._nativeModule?.addListener(eventType);
|
|
let subscription: ?EventSubscription = RCTDeviceEventEmitter.addListener(
|
|
eventType,
|
|
listener,
|
|
context,
|
|
);
|
|
|
|
return {
|
|
remove: () => {
|
|
if (subscription != null) {
|
|
this._nativeModule?.removeListeners(1);
|
|
// $FlowFixMe[incompatible-use]
|
|
subscription.remove();
|
|
subscription = null;
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use `remove` on the EventSubscription from `addListener`.
|
|
*/
|
|
removeListener<TEvent: $Keys<TEventToArgsMap>>(
|
|
eventType: TEvent,
|
|
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => mixed,
|
|
): void {
|
|
this._nativeModule?.removeListeners(1);
|
|
// NOTE: This will report a deprecation notice via `console.error`.
|
|
// $FlowFixMe[prop-missing] - `removeListener` exists but is deprecated.
|
|
RCTDeviceEventEmitter.removeListener(eventType, listener);
|
|
}
|
|
|
|
emit<TEvent: $Keys<TEventToArgsMap>>(
|
|
eventType: TEvent,
|
|
...args: $ElementType<TEventToArgsMap, TEvent>
|
|
): void {
|
|
// Generally, `RCTDeviceEventEmitter` is directly invoked. But this is
|
|
// included for completeness.
|
|
RCTDeviceEventEmitter.emit(eventType, ...args);
|
|
}
|
|
|
|
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(
|
|
eventType?: ?TEvent,
|
|
): void {
|
|
invariant(
|
|
eventType != null,
|
|
'`NativeEventEmitter.removeAllListener()` requires a non-null argument.',
|
|
);
|
|
this._nativeModule?.removeListeners(this.listenerCount(eventType));
|
|
RCTDeviceEventEmitter.removeAllListeners(eventType);
|
|
}
|
|
|
|
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number {
|
|
return RCTDeviceEventEmitter.listenerCount(eventType);
|
|
}
|
|
}
|