CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-expo-modules-core

The core infrastructure for Expo Modules architecture enabling seamless integration between React Native applications and native platform code.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

event-communication.mddocs/

Event Communication

Native-backed event system for real-time communication between JavaScript and native code, with full TypeScript support and automatic lifecycle management.

Capabilities

EventEmitter Class

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");

EventSubscription Interface

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());

Custom Event Emitter Subclasses

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();

Types

/**
 * Base type for events map defining event names and their listener signatures
 */
type EventsMap = Record<string, (...args: any[]) => void>;

docs

app-utilities.md

error-handling.md

event-communication.md

index.md

native-modules.md

native-views.md

permissions.md

platform-utilities.md

shared-memory.md

uuid-generation.md

tile.json