Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Last active March 7, 2024 04:04
Show Gist options
  • Save brennanMKE/1ebba84a0fd7c2e8b481e4f8a5349b99 to your computer and use it in GitHub Desktop.
Save brennanMKE/1ebba84a0fd7c2e8b481e4f8a5349b99 to your computer and use it in GitHub Desktop.
React Native Event Emitter for RCTEventEmitter in Objective-C and Swift

React Native Event Emitter with Swift

You are building a React Native app and want to work in Swift as much as possible while minimizing Objective-C. This task can be a real challenge. Since the Objective-C runtime is leveraged for communicating with the JavaScript context and Swift does not support macros it will be necessary to use Objective-C to bridge React Native and Swift.

Read Exporting Swift to get an understanding of how it can be done. First you need an Objective-C implementation file. In this case it is called ReactNativeEventEmitter.m and it has what you need to make a module and method available to React Native. In ReactNativeEventEmitter.swift you will find the actual implementation with the class and function marked with objc so both are available to the Objective-C runtime.

When your app starts up the module and function will be made available to React Native which will create and instance of the module which is ReactNativeEventEmitter and sets the critical bridge property which allows for communicating between Swift and React Native.

Once React Native initializes ReactNativeEventEmitter it will be registered with EventEmitter so it can be used to send events. Be sure to put all of your events into the array of events returned by supportedEvents as it is used to check for valid events on the React Native side. Events which are not recognized will cause an error.


Brennan Stehling - 2017

class EventEmitter
/// Shared Instance.
public static var sharedInstance = EventEmitter()
// ReactNativeEventEmitter is instantiated by React Native with the bridge.
private static var eventEmitter: ReactNativeEventEmitter!
private init() {}
// When React Native instantiates the emitter it is registered here.
func registerEventEmitter(eventEmitter: ReactNativeEventEmitter) {
EventEmitter.eventEmitter = eventEmitter
}
func dispatch(name: String, body: Any?) {
eventEmitter.sendEvent(withName: name, body: body)
}
/// All Events which must be support by React Native.
lazy var allEvents: [String] = {
var allEventNames: [String] = []
// Append all events here
return allEventNames
}()
}
//
// ReactNativeEventEmitter.m
// See: http://facebook.github.io/react-native/releases/0.43/docs/native-modules-ios.html#exporting-swift
//
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(ReactNativeEventEmitter, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
@end
//
// ReactNativeEventEmitter.swift
//
import Foundation
@objc(ReactNativeEventEmitter)
open class ReactNativeEventEmitter: RCTEventEmitter {
override init() {
super.init()
EventEmitter.sharedInstance.registerEventEmitter(eventEmitter: self)
}
/// Base overide for RCTEventEmitter.
///
/// - Returns: all supported events
@objc open override func supportedEvents() -> [String] {
return EventEmitter.sharedInstance.allEvents
}
}
@aurimasmi
Copy link

aurimasmi commented Sep 23, 2021

add line below to your *-Bridging-Header.h

#import <React/RCTEventEmitter.h>

@buildgreatthings
Copy link

I'm running into an assertion failure error. I followed the original poster's code with minor updates in this comment. After setting it up in my project, I'm getting an assertion failure. Has anyone seen this and know the fix?

*** Assertion failure in -[RCTEventEmitter sendEventWithName:body:](), /Users/alex/Documents/ProjectTest/node_modules/react-native/React/Modules/RCTEventEmitter.m:58
2021-12-14 10:36:50.125246-0800 ProjectTest[623:23190] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: cameraStatus with body: startCollection. RCTCallableJSModules is not set. This is probably because you've explicitly synthesized the RCTCallableJSModules in ReactNativeEventEmitter, even though it's inherited from RCTEventEmitter.'
*** First throw call stack:
(0x180d2104c 0x199395f54 0x1825da834 0x102662bc8 0x1020fd420 0x1020feb10 0x102107c9c 0x102107cdc 0x1835f6bf0 0x183722a4c 0x18349e2c8 0x18353aae0 0x1837cd7c0 0x1832a107c 0x1832d2a84 0x183482318 0x1f1b1b414 0x1832a5c30 0x18329aa1c 0x1840dc7dc 0x18390b7d8 0x183f85008 0x183f845f8 0x180d43020 0x180d53ce0 0x180c8dfe8 0x180c937f4 0x180ca73b8 0x19c63738c 0x1836476a8 0x1833c67f4 0x1020f7680 0x1056f5a24)
libc++abi: terminating with uncaught exception of type NSException

@ramibaraka
Copy link

I'm running into the same error.

Did you find a solution? @awcchungster

@buildgreatthings
Copy link

@ramibaraka I don't know what exactly fixed it. For other features I was adding in, I rebuilt my app a few times and restarted my app on different devices. When I went back to the event emission it just suddenly worked. Sorry I can't be more helpful.

@ramibaraka
Copy link

@awcchungster Thanks for the quick response.
Were you overriding the init for RCTEventemitter?

@buildgreatthings
Copy link

buildgreatthings commented Jan 31, 2022

I added the following to ReactNativeEventEmitter.swift, but I don't think it was a critical fix for the original issue. Everything else was identical to the OP's example

@objc open override class func requiresMainQueueSetup() -> Bool {
     return false
 }

@tibbus
Copy link

tibbus commented Feb 14, 2022

For some reason react-native doesn't listen to the event, I get:
Sending onReady with no listeners registered.

The code in react is:

 const eventEmitter = new NativeEventEmitter(MyModule);

    eventEmitter.addListener("onReady", (event) => {
      console.log(event.eventProperty);
    });

any idea?

@valentingnt
Copy link

For some reason react-native doesn't listen to the event, I get: Sending onReady with no listeners registered.

The code in react is:

 const eventEmitter = new NativeEventEmitter(MyModule);

    eventEmitter.addListener("onReady", (event) => {
      console.log(event.eventProperty);
    });

any idea?

I have the same problem, does anybody know why ? I have exactly the same code as above and I have the following error :
Sending 'RCTMessenger' with no listeners registered.

@tibbus
Copy link

tibbus commented Aug 3, 2022

@valentingnt For me it worked in the end, but I don't remember what the issue was.
As far as I remember it was something on the native side, a module was not properly registered or something.

@valentingnt
Copy link

@tibbus Okay thanks I'm gonna check there, lemme know if you remember anything 😉

@pcsaunak
Copy link

I have the exact same implementation, but when I try using RN 0.69 and above, I get the error
new NativeEventEmitter() requires a non-null argument

Can someone please suggest the cause behind this. Is it that the the native modules are not getting initialised somehow

@pcsaunak
Copy link

I have the exact same implementation, but when I try using RN 0.69 and above, I get the error new NativeEventEmitter() requires a non-null argument

Can someone please suggest the cause behind this. Is it that the the native modules are not getting initialised somehow

Update - I figured out that the problem is with auto-linking. When the library is being installed/added to a project, it is not getting auto-linked and hence the problem.

@williamliangwl
Copy link

williamliangwl commented Oct 18, 2023

thanks for the easy, great code snippet!

just for future reference, especially for those who are developing RN library / package, in which you have separate project with the application, and you are "injecting" your library to the application by doing yarn add files://... for testing purpose, in order to test the new NativeModule() properly, you will need to publish the library / package first and yarn it into the application

I was facing the issue (for 3 days 😢 ) where the iOS part has already sent the event properly, no error message whatsoever, but the event is not received in the Javascript side. Only after I remember that in the Android they have similar issue, then I try to publish my library and yarn in the application side, it is now working well.

@ysfzrn
Copy link

ysfzrn commented Dec 28, 2023

Is there any argument against putting the singleton instance inside the RNEventEmitter itself?

Something like this:

/* RNEventEmitter.m */

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(RNEventEmitter, RCTEventEmitter)
  RCT_EXTERN_METHOD(supportedEvents)
@end
/* RNEventEmitter.swift*/

@objc(RNEventEmitter)
open class RNEventEmitter: RCTEventEmitter {

  public static var emitter: RCTEventEmitter!

  override init() {
    super.init()
    RNEventEmitter.emitter = self
  }

  open override func supportedEvents() -> [String] {
    ["onReady", "onPending", "onFailure"]      // etc. 
  }
}

Usage:

RNEventEmitter.emitter.sendEvent(withName: "onReady", body: [])

or if you have a protocol, say EventApi:

class RNEventApi: EventApi {

  func emitIsReady() {
    RNEventEmitter.emitter.sendEvent(withName: "onReady", body: [])
  }

}

This is perfect, thank you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment