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

Comments

Projects
None yet
@livoras

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

This comment has been minimized.

Show comment
Hide comment
@livoras

livoras 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 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 Jul 13, 2016

@joekim

This comment has been minimized.

Show comment
Hide comment
@joekim

joekim 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.

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

This comment has been minimized.

Show comment
Hide comment
@purplepeng

purplepeng Jul 21, 2016

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

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

@joekim

This comment has been minimized.

Show comment
Hide comment
@joekim

joekim 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.

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

This comment has been minimized.

Show comment
Hide comment
@aaronksaunders

aaronksaunders Jul 22, 2016

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

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

This comment has been minimized.

Show comment
Hide comment
@purplepeng

purplepeng Jul 22, 2016

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@MAGICYA

MAGICYA Aug 8, 2016

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

MAGICYA commented Aug 8, 2016

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

@briandilley

This comment has been minimized.

Show comment
Hide comment
@briandilley

briandilley Aug 8, 2016

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

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

@andybangs

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 10, 2016

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@MacKentoch

MacKentoch Aug 10, 2016

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.

Contributor

MacKentoch commented Aug 10, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@purplepeng

purplepeng 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.

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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 10, 2016

@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 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

This comment has been minimized.

Show comment
Hide comment
@purplepeng

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

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

This comment has been minimized.

Show comment
Hide comment
@basketofsoftkittens

basketofsoftkittens Aug 24, 2016

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

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

@lazarte

This comment has been minimized.

Show comment
Hide comment
@lazarte

lazarte 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

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

This comment has been minimized.

Show comment
Hide comment
@julien-rodrigues

julien-rodrigues Aug 27, 2016

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!

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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs 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

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

This comment has been minimized.

Show comment
Hide comment
@julien-rodrigues

julien-rodrigues 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 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

This comment has been minimized.

Show comment
Hide comment
@julien-rodrigues

julien-rodrigues Aug 27, 2016

@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 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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 27, 2016

@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

  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

This comment has been minimized.

Show comment
Hide comment
@julien-rodrigues

julien-rodrigues 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

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

This comment has been minimized.

Show comment
Hide comment
@VincentdeWit94

VincentdeWit94 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. :(

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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 29, 2016

@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

@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

This comment has been minimized.

Show comment
Hide comment
@TheoGit

TheoGit 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???

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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 30, 2016

@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 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

This comment has been minimized.

Show comment
Hide comment
@TheoGit

TheoGit 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!

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

This comment has been minimized.

Show comment
Hide comment
@andybangs

andybangs Aug 30, 2016

@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.

@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.

@ace68723

This comment has been minimized.

Show comment
Hide comment
@ace68723

ace68723 Aug 31, 2016

@livoras Thank you. It works for me.

@livoras Thank you. It works for me.

@avishayil avishayil referenced this issue in geektimecoil/react-native-onesignal Aug 31, 2016

Closed

Ready for 2.* version of iOS and 3.* version of Android OneSignal SDK. #78

@TheoGit

This comment has been minimized.

Show comment
Hide comment
@TheoGit

TheoGit Sep 6, 2016

@andybangs this is working; I had to make a few tweaks (like NativeAppEventEmitter instead of NativeEventEmitter) but overall it was your suggestion of subclassing RCTEVentEmitter that I finally understood after reading and re-reading; thanks again this thread as been invaluable.

TheoGit commented Sep 6, 2016

@andybangs this is working; I had to make a few tweaks (like NativeAppEventEmitter instead of NativeEventEmitter) but overall it was your suggestion of subclassing RCTEVentEmitter that I finally understood after reading and re-reading; thanks again this thread as been invaluable.

@silhouettes silhouettes referenced this issue in Microsoft/react-native-code-push Sep 24, 2016

Merged

Fix usage of NativeEventEmitter #526

@dabit3

This comment has been minimized.

Show comment
Hide comment
@dabit3

dabit3 Oct 6, 2016

Contributor

@andybangs your example worked for me.

Contributor

dabit3 commented Oct 6, 2016

@andybangs your example worked for me.

@simman simman referenced this issue in rnkit/rnkit-actionsheet-picker Dec 6, 2016

Closed

bridge.eventDispatcher Deprecated, do not use!!! #3

@mnichols mnichols referenced this issue in awslabs/aws-sdk-react-native Dec 18, 2016

Open

Integration of basic authentication to User Pool #27

@tsella tsella referenced this issue in robinpowered/react-native-device-battery Feb 8, 2017

Merged

Support RN 0.40+, switch to NativeAppEventEmitter #6

@hitbear518

This comment has been minimized.

Show comment
Hide comment
@hitbear518

hitbear518 Feb 23, 2017

@andybangs
Actually, no need to use NotificationCenter, RCTBridge has a method called module(for: AnyClass),
which can get an instance of RCTEventEmitter, then you can call your fuction to send event

@andybangs
Actually, no need to use NotificationCenter, RCTBridge has a method called module(for: AnyClass),
which can get an instance of RCTEventEmitter, then you can call your fuction to send event

@xaviraol

This comment has been minimized.

Show comment
Hide comment
@xaviraol

xaviraol Mar 1, 2017

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

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

This comment has been minimized.

Show comment
Hide comment
@hitbear518

hitbear518 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

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

This comment has been minimized.

Show comment
Hide comment

xaviraol commented Mar 2, 2017

thanks @hitbear518 !!!

@davidskaarup

This comment has been minimized.

Show comment
Hide comment
@davidskaarup

davidskaarup Mar 15, 2017

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

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

@ryanwalker

This comment has been minimized.

Show comment
Hide comment
@ryanwalker

ryanwalker Mar 24, 2017

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@ineilzhang

ineilzhang Mar 28, 2017

@hitbear518 I make it out,thanks.

@hitbear518 I make it out,thanks.

@meilers

This comment has been minimized.

Show comment
Hide comment
@meilers

meilers 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.

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

This comment has been minimized.

Show comment
Hide comment
@ZionChang

ZionChang Jun 19, 2017

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?

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

This comment has been minimized.

Show comment
Hide comment
@sandromartis

sandromartis 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.

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

This comment has been minimized.

Show comment
Hide comment
@chourobin

chourobin Jun 27, 2017

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.

Contributor

chourobin commented Jun 27, 2017

@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

This comment has been minimized.

Show comment
Hide comment
@sandromartis

sandromartis 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.

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

This comment has been minimized.

Show comment
Hide comment
@chourobin

chourobin Jun 27, 2017

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

Contributor

chourobin commented Jun 27, 2017

@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

@prayasht

This comment has been minimized.

Show comment
Hide comment
@prayasht

prayasht 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.

prayasht 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

This comment has been minimized.

Show comment
Hide comment
@Liqiankun

Liqiankun Oct 10, 2017

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]

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

This comment has been minimized.

Show comment
Hide comment
@HappyToper

HappyToper Oct 25, 2017

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;

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

This comment has been minimized.

Show comment
Hide comment
@codepujan

codepujan Dec 14, 2017

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

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

@Obaied

This comment has been minimized.

Show comment
Hide comment
@Obaied

Obaied 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 react-native@0.46.0
  • 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?

Obaied 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 react-native@0.46.0
  • 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

This comment has been minimized.

Show comment
Hide comment
@ZionChang

ZionChang Dec 29, 2017

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];

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];

@Obaied

This comment has been minimized.

Show comment
Hide comment
@Obaied

Obaied Dec 29, 2017

@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

Obaied commented Dec 29, 2017

@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

@uerceg uerceg referenced this issue in adjust/react_native_sdk Jan 4, 2018

Merged

Version 4.12.0 #25

@niksoper

This comment has been minimized.

Show comment
Hide comment
@niksoper

niksoper 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 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

This comment has been minimized.

Show comment
Hide comment
@niksoper

niksoper 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...

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

This comment has been minimized.

Show comment
Hide comment
@ZionChang

ZionChang Mar 5, 2018

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

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

@marcmo

This comment has been minimized.

Show comment
Hide comment
@marcmo

marcmo 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.

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

This comment has been minimized.

Show comment
Hide comment
@niksoper

niksoper 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];
  }
}

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

This comment has been minimized.

Show comment
Hide comment
@ZionChang

ZionChang Mar 5, 2018

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

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

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