Module events (old arch)
You may notice, that JS specs contain codegen-related methods, classes, types, etc. to make things more future-proof.
That's because:
- those elements are available since RN older versions (even from v0.65)
- those elements are falling back to "old architecture" implementation (e.g. codegenNativeComponent)
- it introduces type safety for exposed native parts on JS side
- it's much easier to keep single specification on JS side - when old arch will be dropped, there'll be no need to change anything on JS side
So to make it easier, let's use them, to get you more familiar with it 👍
Event emitter declaration
To make it possible for the module to emit events, JS spec needs to declare 2 methods:
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
// ...
export interface Spec extends TurboModule {
addListener(eventName: string): void
removeListeners(count: number): void
}
// TurboModuleRegistry uses NativeModules['MyAwesomeModule'] on old arch
export default TurboModuleRegistry.getEnforcing<Spec>('MyAwesomeModule');
Event emitter implementation in native code
- iOS
- Android
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface NativeMyAwesomeModule : RCTEventEmitter<RCTBridgeModule>
@end
#import "NativeMyAwesomeModule.h"
@implementation NativeMyAwesomeModule {
BOOL hasListeners;
}
RCT_EXPORT_MODULE(NativeMyAwesomeModule)
- (NSArray<NSString *> *)supportedEvents
{
return @[@"decentEvent", @"notGreatEvent"];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
- (void)onSomeNativeEvent:(NSString *)someNativeValue
{
if (hasListeners) {
[self sendEventWithName:@"decentEvent" body:@{ @"value": someNativeValue }];
}
}
@end
To enhance iOS native module with event emitting feature, it has to extend RCTEventEmitter abstract class.
Then in the implementation part, module has to override supportedEvents getter which declares names for events emitted by this module.
It has to also override startObserving and stopObserving methods, which are called when first and last listeners are added/removed.
To emit event, the module should call sendEventWithName:body: method, which is inherited from RCTEventEmitter class.
It takes event name and an object payload (NSDictionary type), which will be sent to JS listener.
- Kotlin
- Java
import com.facebook.react.bridge.ReactMethod
// ...
@ReactMethod
fun addListener(eventName: String?) = Unit
@ReactMethod
fun removeListeners(count: Double) = Unit
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.modules.core.DeviceEventManagerModule
// ...
fun onSomeNativeEvent(someNativeValue: String) {
val reactContext: ReactContext = getReactContextFromTheModule()
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("decentEvent", Arguments.createMap().apply {
putString("value", someNativeValue)
})
}
import com.facebook.react.bridge.ReactMethod;
// ...
@ReactMethod
public void addListener(String eventName) {}
@ReactMethod
public void removeListeners(double count) {}
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
// ...
private void onSomeNativeEvent(String someNativeValue) {
ReactContext reactContext = getReactContextFromTheModule();
WritableMap payload = Arguments.createMap();
payload.putString("value", someNativeValue);
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("decentEvent", payload);
}
To enhance Android native module with event emitting feature, it has to:
- declare
addListenerandremoveListenersmethods - use
RCTDeviceEventEmitterclass for events emitting
To emit event, the module should call emit method on RCTDeviceEventEmitter instance.
The first argument is the event name and the second one is payload object (WritableMap type) which will be sent to JS listener.
It can be constructed with Arguments.createMap utility function.
Event emitter implementation in JS code
import { NativeEventEmitter, Platform } from 'react-native';
import { MyAwesomeModule } from 'my-awesome-module';
const moduleEventEmitter = new NativeEventEmitter(Platform.OS === 'ios' ? MyAwesomeModule : undefined);
// ...
useEffect(() => {
const subscription = moduleEventEmitter.addListener('decentEvent', ({ value }: { value: string }) => {
console.log({ someNativeValue: value });
});
return () => {
subscription.remove();
};
}, []);
To start listening to events emitted from native side, you have to:
- create
NativeEventEmitterinstance - invoke
addListenermethod on that instance, providing the event name and the listener function
When the event is emitted, all listeners for that specific event will be called with the payload value.
When the listener is not needed, it should be removed - as a cleanup, it's recommended to invoke remove method on the object returned from addListener method call.
It will unregister provided listener from emitted native events.