Setup
I’ve a local module that extends RCTEventEmitter and in addition implements a TurboModule spec:
RCTNativeLocalStorage.h:
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeLocalStorage : RCTEventEmitter
@finish
NS_ASSUME_NONNULL_END
RCTNativeLocalStorage.m (partial):
#import "RCTNativeLocalStorage.h"
#import
#import
utilizing namespace fb;
@interface RCTNativeLocalStorage ()
@property (sturdy, nonatomic) NSUserDefaults *localStorage;
@property (sturdy, nonatomic) CBCentralManager *centralManager;
@property (sturdy, nonatomic) NSMutableArray *discoveredDevices;
@property (nonatomic, assign) BOOL hasListeners;
@finish
@implementation RCTNativeLocalStorage
// Register the module
RCT_EXPORT_MODULE(NativeLocalStorage)
// These strategies are required for NativeEventEmitter to work correctly
RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
{
NSLog(@"🎧 addListener known as for: %@", eventName);
}
RCT_EXPORT_METHOD(removeListeners:(double)rely)
{
NSLog(@"🔕 removeListeners known as: %f", rely);
}
// Outline supported occasions
- (NSArray *)supportedEvents {
return @[
@"BluetoothDeviceFound",
];
}
// Occasion listener monitoring
- (void)startObserving {
NSLog(@"✅ startObserving known as - occasions might be emitted");
self.hasListeners = YES;
}
- (void)stopObserving {
NSLog(@"⚠️ stopObserving known as - occasions won't be emitted");
self.hasListeners = NO;
}
- (instancetype)init {
if (self = [super init]) {
_localStorage = [[NSUserDefaults alloc] initWithSuiteName:@"local-storage"];
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
_discoveredDevices = [NSMutableArray new];
_hasListeners = NO;
}
return self;
}
+ (BOOL)requiresMainQueueSetup {
return NO;
}
// TurboModule implementation
- (std::shared_ptr<:turbomodule>)getTurboModule:(const fb::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<:nativelocalstoragespecjsi>(params);
}
// MARK: - TurboModule Strategies
- (NSString * _Nullable)getItem:(NSString *)key {
return [self.localStorage stringForKey:key];
}
- (void)setItem:(NSString *)worth key:(NSString *)key {
[self.localStorage setObject:value forKey:key];
}
- (void)removeItem:(NSString *)key {
[self.localStorage removeObjectForKey:key];
}
- (void)clear {
NSDictionary *allItems = [self.localStorage dictionaryRepresentation];
for (NSString *key in allItems.allKeys) {
[self.localStorage removeObjectForKey:key];
}
}
// Export the startScan methodology to make it accessible to JavaScript
RCT_EXPORT_METHOD(startScan) {
NSLog(@"✅ startScan triggered from JavaScript");
if (_centralManager.state != CBManagerStatePoweredOn) {
NSLog(@"❌ Bluetooth not powered on");
return;
}
// Present an alert to confirm the strategy was known as
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Scan"
message:@"startScan called!"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okay = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:ok];
UIViewController *root = UIApplication.sharedApplication.keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:nil];
});
[_discoveredDevices removeAllObjects];
[_centralManager scanForPeripheralsWithServices:nil options:nil];
}
// Central Supervisor Delegates
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
swap (central.state) {
case CBManagerStatePoweredOn:
NSLog(@"✅ Bluetooth is powered on.");
break;
case CBManagerStatePoweredOff:
NSLog(@"❌ Bluetooth is powered off.");
break;
default:
NSLog(@"⚠️ Bluetooth state modified: %ld", (lengthy)central.state);
break;
}
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSString *deviceName = peripheral.title ?: @"Unknown";
NSString *deviceId = peripheral.identifier.UUIDString;
NSDictionary *deviceInfo = @{
@"title": deviceName,
@"id": deviceId
};
BOOL alreadyExists = NO;
for (NSDictionary *existingDevice in _discoveredDevices) {
if ([existingDevice[@"id"] isEqualToString:deviceId]) {
alreadyExists = YES;
break;
}
}
if (!alreadyExists) {
[_discoveredDevices addObject:deviceInfo];
NSLog(@"✅ Machine found: %@", deviceInfo);
// Ship occasion straight on the principle thread
dispatch_async(dispatch_get_main_queue(), ^{
// The hasListeners test is necessary to keep away from the warning
if (self.hasListeners) {
NSLog(@"🚀 Sending BluetoothDeviceFound occasion");
[self sendEventWithName:@"BluetoothDeviceFound" body:deviceInfo];
} else {
NSLog(@"⚠️ No listeners registered for BluetoothDeviceFound occasion");
}
});
}
}
@finish
Javascript Code:
const { NativeLocalStorage } = NativeModules;
const eventEmitter = new NativeEventEmitter(NativeLocalStorage);
useEffect(() => {
console.log('Organising occasion listener...');
const subscription = eventEmitter.addListener(
'BluetoothDeviceFound',
(deviceInfo) => {
console.log('Machine discovered:', deviceInfo);
// Replace state...
}
);
console.log('Beginning scan...');
NativeLocalStorage.startScan();
return () => subscription.take away();
}, []);
Console output after i set off begin scan
✅ Bluetooth is powered on.
'`new NativeEventEmitter()` was known as with a non-null argument with out the required `addListener` methodology.', { [Component Stack] title: 'Part Stack' }
'`new NativeEventEmitter()` was known as with a non-null argument with out the required `removeListeners` methodology.', { [Component Stack] title: 'Part Stack' }
✅ startScan triggered from JavaScript
✅ Machine found: {
id = "E2DEF552-4C7E-FA6F-1CC3-3F6B0DE3CC31";
title = Unknown;
}
⚠️ No listeners registered for BluetoothDeviceFound occasion
⚠️ No listeners registered for BluetoothDeviceFound occasion
⚠️ No listeners registered for BluetoothDeviceFound occasion
The Drawback
Unable to ship occasions from iOS native code.
The identical Code works with android and I’m able to ship information to the occasion listeners.
What do you suppose I’m lacking right here?
Official docs hyperlink: https://reactnative.dev/docs/0.77/legacy/native-modules-ios#sending-events-to-javascript