React Native Sending Events to JavaScript in Swift

Send event to js from swift or objective-c

I figured it out

Warning: this solution uses a deprecated method react native method - I could not figure out how to "properly" inherit from the RCTEventEmitter and send an event... every time I tried to the _bridge would end up being nil

Make sure Swift is bridged to Objective C (if you're using swift to send the event to javascript)

Do Not create instances of the exported Native modules (whether they be written in Swift or Objective C)

Let React Native's underlying implementation do this and for each and every class that needs to send an event, export that particular Native Class Objective C Implementation code or Swift code (the Native Module) to React-Native. This allows the javascript to be able to listen to the event

var publicBridgeHelperInstance = PublicBridgeHelper()  //instantiate the  the objective c class from inside the .swift file to use later when needing to get a reference to the bridge to send an event to javascript written in react native

@objc(DeviceManager) //export swift module to objective c
class DeviceManager: NSObject {

@objc(deviceDidComeOnline:) //expose the function to objective c
public func deviceDidComeOnline(_ device: GCKDevice) {
//imagine this deviceDidComeOnline function gets called from something from the Native code (totally independent of javascript) - honestly this could be called from a native button click as well just to test it works...

//emit an event to a javascript function that is a in react native Component listening for the event like so:

//1. get a reference to the bridge to send an event through from Native to Javascript in React Native (here is where my custom code comes in to get this to actually work)
let rnBridge = publicBridgeHelperInstance.getBridge() //this gets the bridge that is stored in the AppDelegate.m file that was set from the `rootView.bridge` variable (more on this later)

//(if you want to print the bridge here to make sure it is not `nil` go ahead:
print("rnBridge = \(rnBridge)")

//2. actually send the event through the eventDispatcher
rnBridge?.eventDispatcher().sendAppEvent(withName: "test", body: "testBody data!!!")
}
}

in AppDelegate.h put (additionally to the code that was already in the file)

#import "YourProjectsBridgingHeaderToMakeThisCodeAvailableInSwift.h"  //replace this with your actual header you created when creating a swift file (google it if you dont know how to bridge swift to objective c)

@interface PublicBridgeHelper: NSObject
-(RCTBridge*)getBridge;
@end

in AppDelegate.m put (in addition to the code that was already in the file)

#import <React/RCTRootView.h>

RCTBridge *rnBridgeFromRootView;

@implementation PublicBridgeHelper //this is created to SIMPLY return rnBridgeFromRootView defined above over to my Swift class when actually sending the event to javascript that defines a react native Component
-(RCTBridge*)getBridge {
NSLog(@"rnBridgeFromRootView = @%@", rnBridgeFromRootView);
return rnBridgeFromRootView;
}

important - also make sure to add the following line of code to the Objective C .h's bridging header to make this PublicBridgeHelper definition available to be used in the .swift code

#import "AppDelegate.h"

finally,

now to show you how to set the rnBridgeFromRootView variable used in AppDelegate.m (that gets returned and used in the .swift code right before sending the event to javascript)

open AppDelegate.m and in the method body of

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

include the following after the line of code that instantiates the rootView variable

i.e. after the line that probably looks like

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

add:

rnBridgeFromRootView = rootView.bridge  //set the bridge to  be exposed and returned later and used by the swift class

Now to explain the publicBridgeHelperInstance.getBridge() part that is in the .swift file

publicBridgeHelper is an instance of an objective c class which allows the swift class ability to get a reference to the react native bridge

If you are still having problems understanding my answer after reading this I made a video over it and you can watch it here:

https://www.youtube.com/watch?v=GZj-Vm9cQIg&t=9s

React Native - Sending events from Native to JavaScript in AppDelegate (iOS)

RCTBridge creates new instances of each module class when it starts up, so when you export your AppDelegate as a bridge module, you're telling the bridge to create a new AppDelegate, and give it a bridge instance.

iOS also creates an instance of AppDelegate when your application launches, but the instance created by iOS isn't the same instance created by RCTBridge.

So basically, you have two instances of AppDelegate: the one you're trying to access self.bridge from, which wasn't created by RCTBridge and so doesn't have a reference to it, and the one created by RCTBridge, which has a bridge, but isn't the delegate for your UIApplication, and isn't running your code.

You have a few options:

1) you could pass your AppDelegate instance into the bridge when you create it by using the RCTBridgeDelegate's extraModules method. This lets you tell the bridge to use an existing instance of a module, instead pf creating a new one.

2) you could access the bridge via your RCTRootView instead of making your AppDelegate into a module so that it gets given a self.bridge property.

3) Move the logic that needs to talk to the bridge out of AppDelegate into a new module. If it needs to be triggered by an event inside AppDelegate, use NSNotifications to communicate with the module instance (we use this pattern for RCTPushNotificationManager).

Of these options,

Option 1) is probably the most complicated to do correctly.

Option 2) is probably the easiest to do because you presumably already have an instance of RCTRootView in your AppDelegate that you can reference.

Option 3) is ideal from a technical perspective, because it prevents you from accidentally sending events before the bridge is properly initialized (which might crash, or behave unexpectedly).

Listening for events in react native ios

I've tried dispatching events and it seems bridge is not initialised when you create new EventEmitter instances manually by using [EventEmitter alloc] init]

You should let react-native create instances. I checked native components and they're using -(void)setBridge:(RCTBridge *)bridge method to do initialisation work. Please check out RCTLinkingManager to see an example. It's using NSNotificationCenter to handle events.

// registering for RCTOpenURLNotification evet when the module is initialised with a bridge
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleOpenURLNotification:)
name:RCTOpenURLNotification
object:nil];
}

// emitting openURL event to javascript
- (void)handleOpenURLNotification:(NSNotification *)notification
{
[_bridge.eventDispatcher sendDeviceEventWithName:@"openURL"
body:notification.userInfo];
}

// creating RCTOpenURLNotification event to invoke handleOpenURLNotification method
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
NSDictionary<NSString *, id> *payload = @{@"url": URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
object:self
userInfo:payload];
return YES;
}


Related Topics



Leave a reply



Submit