Native-backed event system for real-time communication between JavaScript and native code, with full TypeScript support and automatic lifecycle management.
A native-backed event emitter that provides consistent API for emitting and listening to events with full TypeScript support.
/**
* A class that provides a consistent API for emitting and listening to events.
* Implementation is written in C++ and common for all platforms.
* Events are emitted synchronously and any returned values are ignored.
*/
class EventEmitter<TEventsMap extends EventsMap = Record<never, never>> {
/**
* Creates a new event emitter instance
*/
constructor();
/**
* @deprecated Creating from existing object not necessary as of SDK 52
*/
constructor(object: EventEmitter);
/**
* Adds a listener for the given event name
* @param eventName - Name of the event to listen for
* @param listener - Function to call when event is emitted
* @returns Subscription object for removing the listener
*/
addListener<EventName extends keyof TEventsMap>(
eventName: EventName,
listener: TEventsMap[EventName]
): EventSubscription;
/**
* Removes a specific listener for the given event name
* @param eventName - Name of the event
* @param listener - The exact listener function to remove
*/
removeListener<EventName extends keyof TEventsMap>(
eventName: EventName,
listener: TEventsMap[EventName]
): void;
/**
* Removes all listeners for the given event name
* @param eventName - Name of the event to clear
*/
removeAllListeners(eventName: keyof TEventsMap): void;
/**
* Synchronously calls all listeners attached to the event
* @param eventName - Name of the event to emit
* @param args - Arguments to pass to the listeners
*/
emit<EventName extends keyof TEventsMap>(
eventName: EventName,
...args: Parameters<TEventsMap[EventName]>
): void;
/**
* Returns the number of listeners for the given event
* @param eventName - Name of the event
* @returns Number of active listeners
*/
listenerCount<EventName extends keyof TEventsMap>(eventName: EventName): number;
/**
* Called automatically when first listener is added
* Override in subclass for additional setup
* @param eventName - Name of the event being observed
*/
startObserving?<EventName extends keyof TEventsMap>(eventName: EventName): void;
/**
* Called automatically when last listener is removed
* Override in subclass for cleanup
* @param eventName - Name of the event no longer observed
*/
stopObserving?<EventName extends keyof TEventsMap>(eventName: EventName): void;
}Usage Examples:
import { EventEmitter, type EventSubscription } from "expo-modules-core";
// Define event types for type safety
type MyEvents = {
userLogin: (userId: string, timestamp: number) => void;
dataReceived: (data: { id: string; content: string }) => void;
error: (error: Error) => void;
};
// Create typed event emitter
const emitter = new EventEmitter<MyEvents>();
// Add listeners with full type safety
const loginSubscription = emitter.addListener("userLogin", (userId, timestamp) => {
console.log(`User ${userId} logged in at ${timestamp}`);
});
const dataSubscription = emitter.addListener("dataReceived", (data) => {
console.log(`Received data: ${data.content}`);
});
// Emit events with type checking
emitter.emit("userLogin", "user123", Date.now());
emitter.emit("dataReceived", { id: "msg1", content: "Hello World" });
// Check listener count
console.log(emitter.listenerCount("userLogin")); // 1
// Remove specific listeners
loginSubscription.remove();
// Remove all listeners for an event
emitter.removeAllListeners("dataReceived");Subscription object returned when adding event listeners, providing a convenient way to remove listeners.
/**
* A subscription object that allows convenient removal of event listeners
*/
interface EventSubscription {
/**
* Removes the event listener associated with this subscription
* After calling this, the listener will no longer receive events
*/
remove(): void;
}Usage Examples:
import { EventEmitter } from "expo-modules-core";
const emitter = new EventEmitter<{ message: (text: string) => void }>();
// Store subscription for later cleanup
const subscription = emitter.addListener("message", (text) => {
console.log("Message:", text);
});
// Later, remove the listener
subscription.remove();
// Multiple subscriptions can be stored and cleaned up
const subscriptions: EventSubscription[] = [
emitter.addListener("message", handler1),
emitter.addListener("message", handler2),
emitter.addListener("message", handler3),
];
// Clean up all subscriptions
subscriptions.forEach(sub => sub.remove());Create custom event emitters with automatic lifecycle management by extending EventEmitter.
import { EventEmitter } from "expo-modules-core";
// Define your event types
type NetworkEvents = {
connected: () => void;
disconnected: (reason: string) => void;
dataReceived: (data: ArrayBuffer) => void;
};
class NetworkEventEmitter extends EventEmitter<NetworkEvents> {
private connection: WebSocket | null = null;
// Override startObserving for setup when first listener added
startObserving(eventName: keyof NetworkEvents) {
console.log(`Starting to observe ${eventName}`);
if (eventName === 'connected' && !this.connection) {
this.setupConnection();
}
}
// Override stopObserving for cleanup when last listener removed
stopObserving(eventName: keyof NetworkEvents) {
console.log(`Stopped observing ${eventName}`);
if (eventName === 'connected' && this.listenerCount('connected') === 0) {
this.teardownConnection();
}
}
private setupConnection() {
// Connection setup logic
this.connection = new WebSocket('ws://example.com');
this.connection.onopen = () => this.emit('connected');
this.connection.onclose = (event) => this.emit('disconnected', event.reason);
}
private teardownConnection() {
// Connection cleanup logic
this.connection?.close();
this.connection = null;
}
}
// Usage
const networkEmitter = new NetworkEventEmitter();
// Adding first listener triggers startObserving
const subscription = networkEmitter.addListener('connected', () => {
console.log('Network connected!');
});
// Removing last listener triggers stopObserving
subscription.remove();/**
* Base type for events map defining event names and their listener signatures
*/
type EventsMap = Record<string, (...args: any[]) => void>;