Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What is the method can be used to send an event from native module to JS? #8714

Closed
livoras opened this issue Jul 12, 2016 · 56 comments
Closed
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@livoras
Copy link

livoras commented Jul 12, 2016

According to React Native documentation, you can use sendAppEventWithName to send an event from native code to JS. But in my XCode, code suggestions tell me that this method is deprecated.

image

This issue indicates that sendDeviceEventWithName should work but actually it's also deprecated.

What is the proper way to send an event to JS?

@livoras
Copy link
Author

livoras commented Jul 13, 2016

I figured it out by reading its source code. Using the RCTEventEmitter class.

MyModule.h

#import "RCTEventEmitter.h"
#import "RCTBridgeModule.h"

@interface MyModule : RCTEventEmitter <RCTBridgeModule>

@end
MyModule.m

@implementation MyModule

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents {
  return @[@"sayHello"];
}

- (void)tellJS () {
   [self sendEventWithName:@"sayHello" body:@"Hello"];
}

@end

So you can send an event called sayHello with data Hello to JavaScript by calling the tellJS method.

In JavaScript side, you have to use the NativeModules module to get this native module and wrap it in NativeEventEmitter class so that you can receive events.

import { NativeModules, NativeEventEmitter } from 'react-native'

const myModuleEvt = new NativeEventEmitter(NativeModules.MyModule)
myModuleEvt.addListener('sayHello', (data) => console.log(data))

@livoras livoras closed this as completed Jul 13, 2016
@joekim
Copy link

joekim commented Jul 14, 2016

@livoras

First issue, shouldn't:

 [sendEventWithName:@"sayHello" body:@"Hello"];

Be:

 [self sendEventWithName:@"sayHello" body:@"Hello"];

Second issue: RCTEventEmitter.m is throwing an error:

- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
  RCTAssert(_bridge != nil, @"bridge is not set. This is probably because you've "
            "explicitly synthesized the bridge in %@, even though it's inherited "
            "from RCTEventEmitter.", [self class]);

  if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
    RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
                eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
  }
  if (_listenerCount > 0) {
    [_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
                      args:body ? @[eventName, body] : @[eventName]];
  } else {
    RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName);
  }
}

_bridge is nil and it's throwing an exception. Should we open another ticket on fixing the documentation on the proper way to do this? I think it's still not fully clear.

@purplepeng
Copy link

I have the same problem, the bridge is nil. Have you resolve it? @joekim

@joekim
Copy link

joekim commented Jul 21, 2016

@purplepeng I ended up just creating a native module and using callbacks instead of using events. Haven't tested it again recently.

@aaronksaunders
Copy link

I got it to work in swift... here is the snippets

// in my module...
@objc(YourModule)
class YourModule: RCTEventEmitter {


  override func supportedEvents() -> [String]! {
    return ["UploadProgress"]
  }

further down in the class

    // A progress event occurred, notify the user...
    uploadTask.observeStatus(.Progress) { (snapshot:FIRStorageTaskSnapshot) in

      if let progress = snapshot.progress {

        let percentComplete =  Double(progress.completedUnitCount) / Double(fs)

        let ret : [String:AnyObject] = [
          "completedUnitCount": String(progress.completedUnitCount),
          "percentComplete": String(percentComplete),
          "totalUnitCount": String(fs)
        ]

        self.sendEventWithName("UploadProgress", body: ret )
      }
    }

In my react-native code

    const myModuleEvt = new NativeEventEmitter(NativeModules.YourModule)
    var subscription = myModuleEvt.addListener(
      'UploadProgress',
      (progress) => {
        console.log("UploadProgress")
        console.log(JSON.stringify(progress, null, 2))
      }
    );

@purplepeng
Copy link

@joekim @aaronksaunders @livoras Thank you. It works for me. I realized the process of sent event from native to JS in two separate module before, so it doesn't work.

@MAGICYA
Copy link

MAGICYA commented Aug 8, 2016

so , the start of 'send event from iOS to javascript' is still javascript,am i right? 3Q

@briandilley
Copy link

what about android? this seems to create module scoped events, but how do we do this in android?

@andybangs
Copy link

@purplepeng can you explain what you meant by "I realized the process of sent event from native to JS in two separate module before, so it doesn't work."

I am trying to send an event to JS and keep seeing the error that the bridge is not set.

@MacKentoch
Copy link
Contributor

@aaronksaunders thanks for tip but how to deal a viewmanager in the same time?

@objc(RNAnalogClockSwift)
class RNAnalogClockManager: RCTViewManager, RCTEventEmitter {

throws: Multiple inheritance from classes 'RCTViewManager' and 'RCTEventEmitter'

Sorry, I'm a bit confused how to deal sending event since this deprecation.
Previous way was far easier to figure out and implement.

@purplepeng
Copy link

purplepeng commented Aug 10, 2016

@andybangs
I have resolved the problem with following code.

#import "RCTBridgeModule.h"
#import "RCTEventEmitter.h"

@interface RCTUserManager : RCTEventEmitter <RCTBridgeModule>
@end
@implementation RCTUserManager

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
  return @[AuthorizationWasFailedNotification,AuthorizationWasSucceedNotification,AuthorizationWasInvalidNotification,AuthorizationWasCancelledNotification];
}

RCT_EXPORT_METHOD(signinWithUserName:(NSString *)userName
                  password:(NSString *)password
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  [self.userModel signinWithUserName:userName
                            password:password
                          completion:^(STIHTTPResponseError *e) {
                            if ( e ) { 
                              NSError * error;
                              reject([NSString stringWithFormat:@"%@",@(e.code)],e.message,error);
                            }
                            else
                            {
                              NSDictionary * data = @{@"token":self.userModel.token,@"user":[self.userModel.user JSONStringRepresentation]};
                              resolve([data JSONStringRepresentation]);
                              [self sendEventWithName:AuthorizationWasSucceedNotification body:[data JSONStringRepresentation]];
                            }
                          }];
}

But,I added
[self sendEventWithName:AuthorizationWasSucceedNotification body:[data JSONStringRepresentation]];
in other module before,not in this RCTUserManager,so it didn't work. Hope can help you.

@andybangs
Copy link

@purplepeng it looks like you are subclassing RCTEventEmitter, but actually using a promise to interact with React Native. Are you successfully listening to your [self sendEventWithName:body:]; call in JS, or just relying on your promise resolve block? Or both?

@purplepeng
Copy link

purplepeng commented Aug 11, 2016

@andybangs
Both. In my react-native code, the part of event as below:

componentDidMount() {
    const authModuleEvent = new NativeEventEmitter(NativeModules.UserManager)
    signinSubscription = authModuleEvent.addListener('AuthorizationWasSucceedNotification',
     (reminder) => {
     ...
});
}
componentWillUnmount() {
    signinSubscription.remove();
}

@basketofsoftkittens
Copy link

@purplepeng any word on how to do this on the android side?

@lazarte
Copy link

lazarte commented Aug 25, 2016

bridge still nil. What is wrong with this?

#import "RCTBridgeModule.h"
#import "RCTEventEmitter.h"

@interface HelperManager : RCTEventEmitter <RCTBridgeModule>

- (void)sendURLSchemeParameters:(NSURL *)url;

@end
#import "HelperManager.h"

@implementation HelperManager

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents {
  return @[@"URL_SCHEME"];
}

RCT_EXPORT_METHOD(sendURLSchemeParameters:(NSURL *)url) {
  [self sendEventWithName:@"URL_SCHEME" body:@{}];
}
@end

@julien-rodrigues
Copy link

Can we please have an insight on how to handle this now? I made a bridge using the doc but knowing that this method is deprecated, I'm not really confident...
I think this needs to be documented as this means every lib using this are going to break when the update removing the support of the deprecated methods is going to land!

@andybangs
Copy link

andybangs commented Aug 27, 2016

@lazarte @julien-rodrigues: Here is a stripped down version of a functioning subclass of RCTEventEmitter in my app: https://gist.github.com/andybangs/c4651a3916ebde0df1c977b220bbec4b

@julien-rodrigues
Copy link

julien-rodrigues commented Aug 27, 2016

@andybangs Thanks a lot for your gist. I'm a complete noob in Objective C but this is exactly what I'm trying to achieve.

I'm receiving a Firebase dynamic link on application's openUrl and I need to be able to fire an event with the "decoded" link that my RN bridge would listen and forward it to the JS side.

I have 2 question tho. Do you start listening on the Objective C event in the init of your class?
And when do you fire the method to the NotificationCenter?

Thanks again

@julien-rodrigues
Copy link

@andybangs Just tried to replace my implementation with yours and I keep having this:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'bridge is not set. This is probably because you've explicitly synthesized the bridge in TestClass, even though it's inherited from RCTEventEmitter.

@andybangs
Copy link

@julien-rodrigues

  1. I expose a startListening method to be called in a RN component’s componentWillMount method
  2. My subclass of RCTEventEmitter handles firing notifications

To continue with my example above, I am not explicitly instantiating my GSEventEmitter class anywhere because RN creates the instance for me. I import the GSEventEmitter header file in whatever other class I want to emit an event from to use it. Here is another stripped down gist of how I am doing this: https://gist.github.com/andybangs/5f1ee78247b0346e24d55119c6a02afa

Hope this helps, and let me know if you have any other questions.

@julien-rodrigues
Copy link

julien-rodrigues commented Aug 27, 2016

@andybangs Thanks a lot for your time 👍

Yes that's really more clear to me now lol
This implementation is different from what I've done and I understand now why I got conflicts.

I was trying to go full native. I was attaching the listener by myself in AppDelegate and then fired the event when the FirebaseSDK was done computing the incoming link. Which then fired the JS event. So either the event was fired before the JS side was listening or the JS handler was not attached on the right instance(?, don't know if that's possible).

Either way I like your idea, I'm going to try it out. Thanks for sharing

@VincentdeWit94
Copy link

VincentdeWit94 commented Aug 29, 2016

Facing the same problem, subclassed the RCTEventEmitter but stuck on the bridge is nil assertion error (bridge is not set. This is probably because you've explicitly synthesized the bridge in ReactNativeEventManager, even though it's inherited from RCTEventEmitter.).

#import <React/RCTEventEmitter.h>

@interface ReactNativeEventManager : RCTEventEmitter

-(void)postEvent:(NSNotification *)notification;

@end

#import "ReactNativeEventManager.h"

@implementation ReactNativeEventManager
RCT_EXPORT_MODULE();

-(void)postEvent:(NSNotification *)notification {
    NSString *eventName = notification.userInfo[@"name"];
    [self sendEventWithName:eventName body:[notification userInfo]];
}

- (NSArray<NSString *> *)supportedEvents {
    return @[@"add-exchange", @"open-exchange"];
}

@end

Anybody an idea on how to solve this? Did try all of the above solutions, but none of them seem to work. :(

@andybangs
Copy link

@VincentdeWit94 did you check out the gists I posted above that utilize NSNotificationCenter?
Example subclass of RCTEventEmitter: https://gist.github.com/andybangs/c4651a3916ebde0df1c977b220bbec4b
Example usage: https://gist.github.com/andybangs/5f1ee78247b0346e24d55119c6a02afa

@TheoGit
Copy link

TheoGit commented Aug 29, 2016

This entire thread has been helpful, appreciate it, but I'm not quite there yet.
@andybangs if I'm not mistaken you created a .m file just for raising the event (handleBeaconSightingNotification); then you import that into your .m file that will emit the event calling the GSEventEmitter method???

@andybangs
Copy link

@TheoGit that sounds about right. It may not be how usage is shown in the docs, but is the solution I'm currently relying on to get past that "bridge is not set..." error that others have also posted about here.

My subclass of RCTEventEmitter, GSEventEmitter, is modeled after how RCTLinkingManager in the RN codebase subclasses RCTEventEmitter. Although the code itself is not up to date, the idea to try this came from the accepted answer of this SO post: http://stackoverflow.com/questions/36092903/listening-for-events-in-react-native-ios.

In my application, GSEventEmitter handles all native events that need to be emitted to JS, so I import and use GSEventEmitter.h in a number of other classes. In the example above I use it in GSBeaconManager.m to emit beacon sightings with the following call:

[GSEventEmitter application:[UIApplication sharedApplication] beaconSighted:beaconID];

Here is another example:

I already have native code for registering notification settings, so instead of using the RN PushNotificationIOS abstraction, I use my Objective-C code and a method I define in GSEventEmitter called + (BOOL)application:(UIApplication *)application notificationsRegistered:(UIUserNotificationSettings *)notificationSettings; that I call in my AppDelegate and listen for in JS.

// AppDelegate.m
...
- (void)application:(UIApplication *)application 
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
  [GSEventEmitter application:[UIApplication sharedApplication] 
      notificationsRegistered:notificationSettings];
}
...
// Welcome.js
...
componentWillMount() {
  const { GSEventEmitter } = NativeModules;
  const { NOTIFICATIONS_REGISTERED, SIGHTED_BEACON } = GSEventEmitter;
  const { addBeacon } = this.props.actions;

  this.eventEmitter = new NativeEventEmitter(GSEventEmitter);
  this.eventEmitter.addListener(NOTIFICATIONS_REGISTERED, this.handleNotificationsRegistered);
  this.eventEmitter.addListener(SIGHTED_BEACON, (data) => addBeacon(data.payload));
}

componentDidMount() {
  const { GSNotificationManager } = NativeModules;
  GSNotificationManager.registerNotifications();
}

componentWillUnmount() {
  this.eventEmitter.remove();
}

handleNotificationsRegistered() {
  const { GSBeaconManager } = NativeModules;
  GSBeaconManager.startListening();
}
...

@TheoGit
Copy link

TheoGit commented Aug 30, 2016

@andybangs I really appreciate the time/explanation - will do my best to reproduce this; the only part confusing for me is the delegate (GMBLBeaconManagerDelegate) - thank you again!

@andybangs
Copy link

@TheoGit: No problem. I included the GMBLBeaconDelegate protocol only as an example of how I am using my subclass of RCTEventEmitter. In that example, the method – beaconManager:didReceiveBeaconSighting: tells the instance of GMBLBeaconManager when a beacon is sighted. The relevant part to this discussion, is the call in that method [GSEventEmitter application:[UIApplication sharedApplication] beaconSighted:beaconID]; which emits an event that I am listening for in JS.

@xaviraol
Copy link

xaviraol commented Mar 1, 2017

@hitbear518 Can you give more insights on how to use module(for: ) and get the instance of RCTEventEmitter? Thanks a lot

@hitbear518
Copy link

hitbear518 commented Mar 1, 2017

@xaviraol Here's what i did:

  1. subcass RCTEventEmitter
class FooModule: RCTEventEmitter {
...
}
  1. export it to js
@interface RCT_EXTERN_MODULE(FooModule, NSObject)
...
@end

3.get the RCTBridge(I saved it as singleton), then get the module to send event

if let fooModule = MyRCTBridgeDelegate.sharedInstance.bridge.module(for: FooModule) as? FooModule {
    fooModule.sendMyEvent()
}

then you can subscript the event from js code

Also, checkout the master branch doc in react-native official site for more info:
Sending Events to JavaScript

@xaviraol
Copy link

xaviraol commented Mar 2, 2017

thanks @hitbear518 !!!

@davidskaarup
Copy link

@hitbear518 would it be possible to provide a more complete example of your implementation?

@ryanwalker
Copy link

@aaronksaunders How are you instantiating the RCTEventEmitter (YourModule)? I have my code pretty much exactly the same as yours, but if I instantiate MyEventEmitter, the bridge variable is null and the app crashes. I've read that I should "let react instiatate modules" but I'm not quite clear on how to do this. It seems possible in ObjeC but how to do it in Swift?? I'm missing something simple I'm sure, any help would be appreciated.

@ineilzhang
Copy link

@hitbear518 I make it out,thanks.

@meilers
Copy link

meilers commented May 16, 2017

@aaronksaunders You shouldn't instantiate your emitter yourself. Look here.

"When you used the macro RCT_EXPORT_MODULE() React-Native will instantiate the class for you, and any subsequent alloc/inits will create new instances, unrelated the original. The bridge will not be instantiated in these new instances."

The solution is to override startObserving and stopObserving and listen to NSNotifications.

@ZionChang
Copy link

I used both Objective-C and Swift to test, but I still get "_bridge is nil and it's throwing an exception"

Here's what I did:

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

@interface RNEventManager : RCTEventEmitter <RCTBridgeModule>

@end

// RNEventManager.m
#import "RNEventManager.h"

@implementation RNEventManager

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"ExportDescriptionViewWillAppear"];
}

@end

And I called sendEventWithName:body: in viewWillAppear of ExportDescription.

So did I miss something?

@sandromartis
Copy link

sandromartis commented Jun 27, 2017

Okay, I seem to have managed to get it to work using Swift. It's almost the same as @hitbear518 proposed.

EventEmitter.swift

@objc(EventEmitter)
class EventEmitter: RCTEventEmitter {
  
  @objc
  override func supportedEvents() -> [String] {
    return ["event1", "event2"]
  }
}

EventEmitterBridge.m

#import "React/RCTEventEmitter.h"

@interface RCT_EXTERN_MODULE(EventEmitter, RCTEventEmitter)
@end

And then in my other module I'm doing something like this:

MyOtherModule.swift

var bridge: RCTBridge!

@objc(MyOtherModule)
class MyOtherModule {

  func sendSomeEvent() {
    if let eventEmitter = bridge.module(for: EventEmitter.self) as? EventEmitter {
          self.sendEvent(withName: "event1", body: "Woot!")
    }
  }
}

I don't have a single clue how and why this is working since my iOS / Swift knowledge is basically non-existent, but apparently, the var bridge: RCTBridge! object gets somehow synthesized (is that what this is called?) so it is available in MyOtherModule.

I'm also not sure if I used the module(for:) thingy correctly but it seems to work.

As mentioned before the EventEmitter should not be instantiated manually.

@chourobin
Copy link
Contributor

@sandromartis you should have your "MyOtherModule" inherit RCTEventEmitter (you kind of did with the EventEmitter class already). Then you can use sendEvent from there.

There must be something missing from the code example you've given because I don't understand why bridge: RCTBridge! is available in MyOtherModule.

@sandromartis
Copy link

sandromartis commented Jun 27, 2017

@chourobin The thing is that I cannot make MyOtherModule inherit from RCTEventEmitter because it is already inheriting from RCTViewManager.
That's why I created a new module EventEmitter that inherits from RCTEventEmitter which creates the whole problem of accessing it in MyOtherModule.
So MyOtherModule actually looks like this:

var bridge: RCTBridge!

@objc(MyOtherModule)
class MyOtherModule: RCTViewManager {

  func sendSomeEvent() {
    if let eventEmitter = bridge.module(for: EventEmitter.self) as? EventEmitter {
          self.sendEvent(withName: "event1", body: "Woot!")
    }
  }
}

Now that I'm writing this I realize that bridge: RCTBridge! is available in MyOtherModule because that one is inheriting from RCTViewManager. Also bridge: RCTBridge! is actually not necessary and you can just use self.bridge.module(for: EventEmitter.self).

I assume that if you don't have an RCTViewManager you probably have to set the bridge to a global value in AppDelegate.m to make this work. Something like this:

AppDelegate.h

@property (nonatomic, strong) RCTBridge *bridge;

AppDelegate.m

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  ...

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"LayerTwo"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  
  bridge = rootView.bridge;

  ...

}

@end

Let me know if that makes sense or if I'm talking complete bullshit here. I'm totally new to all this react-native and native iOS development stuff.

@chourobin
Copy link
Contributor

@sandromartis nope that makes sense. just fyi, there is another api for sending events from ui-based modules, and it's by using RCTBubblingEventBlock or RCTDirectEventBlock. I had a gist that summarizes some of this info if it's helpful:

https://gist.github.com/chourobin/f83f3b3a6fd2053fad29fff69524f91c#file-events-ui-md

@prayash
Copy link

prayash commented Jul 15, 2017

I subclassed RCTEventEmitter for a background timer and for some reason the events fire in the background in the emulator but not on my real iOS device. Does anyone know why this happens? sendDeviceEventWithName works perfectly but it sends a deprecation warning.

@Liqiankun
Copy link

Finally, I got the solution.

#import "RNNotification.h"
@implementation RNNotification

RCT_EXPORT_MODULE();

+ (id)allocWithZone:(NSZone *)zone {
    static RNNotification *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

- (NSArray<NSString *> *)supportedEvents
{
    return @[@"EventReminder"];
}

- (void)sendNotificationToReactNative
{
    [self sendEventWithName:@"EventReminder" body:@{@"name": @"name"}];
}

When you use it!

RNNotification *notification = [RNNotification allocWithZone: nil];
[notification sendNotificationToReactNative]

@HappyToper
Copy link

I fix this problem by:

  • (NSArray<NSString*> *)supportedEvents {
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    delegate.MyViewController = self;

    return @[@"CancelEvent", @"OKEvent"];
    }

Globally , call delegate.MyViewController 's function to send events;

@codepujan
Copy link

@Liqiankun Your solution Works like a charm . Much Appreciated :)

@afjoseph
Copy link

afjoseph commented Dec 28, 2017

This doesn't seem to work on versions before 0.47.0.
Its easily reproducible:

  • Make a new app that supports 0.46.0: react-native init TestProject --version [email protected]
  • Use @LiangQiao's singleton to emit events
  • Nothing gets received on JS layer

Tested on the same app with version 0.49.0 and it works like a charm.

I could be wrong but I believe it has to do with these breaking changes (ce6fb33, 53d5504)

Can someone comment on this?

@ZionChang
Copy link

This may help you.

BKLEventEmitter.h

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

@interface BKLEventEmitter : RCTEventEmitter <RCTBridgeModule>
+ (void)emitEventWithName:(NSString *)name andPayload:(NSDictionary *)payload;
@end

BKLEventEmitter.m

@implementation BKLEventEmitter

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents {
    // register name, may be more...
    return @[@"NotificationName"];
}

- (void)startObserving {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    for (NSString *notificationName in [self supportedEvents]) {
        [center addObserver:self
               selector:@selector(emitEventInternal:)
                   name:notificationName
                 object:nil];
    }
}

- (void)stopObserving {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)emitEventInternal:(NSNotification *)notification {
    [self sendEventWithName:notification.name
                   body:notification.userInfo];
}

+ (void)emitEventWithName:(NSString *)name andPayload:(NSDictionary *)payload {
    [[NSNotificationCenter defaultCenter] postNotificationName:name
                                                    object:self
                                                  userInfo:payload];
}

@end

Finally use it!! You must ensure that the name is already registered.
[BKLEventEmitter emitEventWithName:@"NoticationName" andPayload:nil];

@afjoseph
Copy link

@ZionChang Great piece of code!!
Unfortunately, it was my ignorance that left out an integral piece of code in the new 0.46.0 RN app I made. Regardless, I like the static method calls much more than having an extra object. Much appreciated

@niksoper
Copy link

niksoper commented Mar 2, 2018

@ZionChang this looks really good but how do you call the instance method startObserving? I'm pretty new to Objective-C so it's not clear to me.

@niksoper
Copy link

niksoper commented Mar 2, 2018

@ZionChang I read the code for RCTEventEmitter and saw that the startObserving method is called when you call addListener from JavaScript.

My issue is I'm emitting the event before the listener was attached!

Working on it...

@ZionChang
Copy link

@niksoper no need to call addListener which is called in react-native source code, just call emitEventWithName.

@marcmo
Copy link

marcmo commented Mar 5, 2018

just starting out using events to communicate from objective-c to js. this is all pretty confusing at the moment. I have a "working" version but I don't understand it since I have to use NativeEventEmitter and DeviceEventEmitter to receive events from objective-c:

const communicationModuleEmitter = new NativeEventEmitter(CommunicationModule);
const subscription = communicationModuleEmitter.addListener(
  'genericLogEvent',
  (e) => console.log('got a genericLogEvent: ', e),  // <--- this is NOT called but somehow needed
);

along with

const subscribeForNativeEvents = (eventID, callback) => {
  LOG_S().d('Subscribing to ' + eventID);
  DeviceEventEmitter.addListener(eventID, callback);
}; 
subscribeForNativeEvents('genericLogEvent', (event) => { 
  console.log('got a generic event!!!', event); // <--- this is actually called
});

this is the native objective-c side:

RCT_EXPORT_METHOD(foo)
{
  RCTLogInfo(@"foo");
  [self emitMessageToRN:@"genericLogEvent" :@{ @"logMessage": @"event from foo" }];
}
- (void) emitMessageToRN: (NSString *)eventName :(NSDictionary *)params {
  [self sendEventWithName: eventName body: params];
}

this setup will give me the following result:
D got a generic event!!! "type is: object : {"logMessage":"event from foo"}"
anyone knows why I need to subscribe both with DeviceEventEmitter and NativeEventEmitter? I tried to find documentation on this but haven't found a lot.

@niksoper
Copy link

niksoper commented Mar 5, 2018

@ZionChang but with no listeners the event will be useless and emitEventWithName will go nowhere.

My reading of the RCTEventEmitter source tells me that startObserving will only be called once you've added a listener in JavaScript - which is presumably an optimisation:

RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
{
  if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
    RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
                eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
  }
  _listenerCount++;
  if (_listenerCount == 1) {
    [self startObserving];
  }
}

@ZionChang
Copy link

I know that, like I said, such as you can call [BKLEventEmitter emitEventWithName:@"NoticationName" andPayload:nil]; in viewDidAppear which is a native method of UIViewController. And method named addListener will be called by react-native automatically. You can have a try and make a breakpoint to test it.
@niksoper

@facebook facebook locked as resolved and limited conversation to collaborators May 24, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests