Here’s the code

Many times we need to send events to JS from native. Such as a calendar 📅 event mentioned in official documentation. You schedule a meeting, or an event, and then specify a date for it to happen. Or turn off the contribution bike, bluetooth receives a successful shutdown signal. Or an APP like Geofencing, when you enter/leave a geofencing, you need to send events from native to JS.

First, a simple example

Calling a native method sets the native time for a delayed trigger, similar to calling a native setTimeout. After time an event is sent from the native to JS.

First, the UI will have a text box to enter the time, after the user enters the time and clicks OK. The App is going to call the native method and execute the native setTimeout method.

The native method called by the App is a promise method on the front end. So, this method can be called with async-await.

An event listener is registered in the JS section, which executes the JS code as soon as the native event is received. In this case, I just output a log for simplicity.

Since events are received and sent from native, a native module is essential. For those of you who aren’t familiar with this part, you can go to this is iOS and this is Android

Point need to be updated, now officially recommended at the time of implementation, the Android native modules using ` ReactContextBaseJavaModule `. The main reason is type safety.Copy the code

Implement a native module in iOS

// header file
@interface FillingHoleModule: RCTEventEmitter<RCTBridgeModule>

@end

// implementation
#import "FillingHoleModule.h"

@implementation FillingHoleModule

RCT_EXPORT_MODULE(FillingHoleModule)

RCT_EXPORT_METHOD(sendEventInSeconds: (NSUInteger) seconds resolver:(RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject) {
  @try {
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * seconds);
    dispatch_after(delay, dispatch_get_main_queue(), ^(void) {[self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole".@"with": @"RN"}];
    });
    
    NSLog(@"Resolved");
    resolve(@"done");
  } @catch (NSException *exception) {
    NSLog(@"Rejected %@", exception);
    reject(@"Failed".@"Cannot setup timeout".nil); }} - (NSArray<NSString *> *)supportedEvents {
  return@ [@"FillingHole"];
}

@end

Copy the code
The module header file is omitted here.Copy the code

1: In the header file, you can see that the native module inherits RCTEventEmitter.

2: Therefore, the supportedEvents method of this class should be implemented during implementation. Add the name of the event we want to send from the native. Such as:

- (NSArray<NSString *> *)supportedEvents {
  return@ [@"FillingHole"];
}
Copy the code

3: A try-catch in sendEventInSeconds can assist calls to resolve and reject to fulfill the JS promise

4: This is equivalent to setTimeout in objc:

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * seconds);
    dispatch_after(delay, dispatch_get_main_queue(), ^(void) {[self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole".@"with": @"RN"}];
    });
Copy the code

Code [self sendEventWithName: @ “FillingHole” body: @ {@ “filling” : @ “hole,” @ “with” : @ “RN”}); Complete the function of sending events from native to JS.

Native modules in Android

public class FillingEventHole extends ReactContextBaseJavaModule {
    FillingEventHole(ReactApplicationContext context) {
        super(context);
    }

    @NonNull
    @Override
    public String getName(a) {
        return "FillingHoleModule";
    }

    @ReactMethod
    public void sendEventInSeconds(long seconds, Promise promise) {
        Log.d("FillEventHole"."Event from native comes in" + seconds);
        try {
            new android.os.Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                @Override
                public void run(a) {
                    WritableMap params = Arguments.createMap();
                    params.putString("filling"."hole");
                    params.putString("with"."RN");
                    FillingEventHole.this.sendEvent("FillingHole", params);
                }
            }, seconds * 1000);

            promise.resolve("Done");
        } catch(Exception e) { promise.reject(e); }}private void sendEvent(String eventName, @Nullable WritableMap params) {
        this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
    }
}
Copy the code

Note: There are more specific native steps than iOS. They are omitted here if you need to refer to the Android native modules section above.

1: sendEventInSeconds is used to receive the time information passed by JS and start Android’s setTimeout. 2: Try-catch is the same as iOS. Resolve and reject are processed together. 3: Android setTimeout:

    new android.os.Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
        @Override
        public void run(a) {
            WritableMap params = Arguments.createMap();
            params.putString("filling"."hole");
            params.putString("with"."RN");
            FillingEventHole.this.sendEvent("FillingHole", params);
        }
    }, seconds * 1000);
Copy the code

Method 4: FillingEventHole. This. Adds (” FillingHole, “params); Call the setEvent method implemented in the native module to send the event. 5: Event sending method:

    private void sendEvent(String eventName, @Nullable WritableMap params) {
        this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);  }Copy the code

The front-end implementation

Before we get to the front end, a few words of nonsense.

Native modules implemented by Android and iOS must have the same name. The module name should have the same name as the native method. You need to make sure the types are the same when calling native methods in the front end. For example, public void sendEventInSeconds(long seconds, Promise Promise) must be numeric when JS is called, otherwise an error will occur. Alternatively, you can provide custom type conversion methods.

On the front end you only need:

import {
  / /...
  NativeEventEmitter,
  NativeModules,
  EmitterSubscription,
} from 'react-native';

const {FillingHoleModule} = NativeModules;  / / 1
const eventEmitter = new NativeEventEmitter(FillingHoleModule); / / 2

const App = () = > {
  useEffect(() = > {
    / / 3
    const eventListener = eventEmitter.addListener('FillingHole'.event= > {
      console.log('You received an event'.JSON.stringify(event));
    });

    listenersRef.current = eventListener;

    return () = > {
      / / 4listenersRef.current? .remove(); }; });/ / 5
  const handlePress = async() = > {console.log('>', text);
    try {
      await FillingHoleModule.sendEventInSeconds(+text);
    } catch (e) {
      console.error('Create event failed, ', e); }};return (
    / /...
  );
}

Copy the code

1: get defined NativeModules from NativeModules 2: initialize NativeEventEmiotter with defined NativeModules 3: add native event listener 4: destroy listener 5 at the end: The setTimeout method of the native event is invoked when the user clicks the button.

React hooks 3> and 4> Are hooks from React hooks.

Another useful tip here is to use useRef to save component event listeners.

The last

When you’re ready, you can run. Shake the phone to enable debug mode to view events received from native.

Emitting from native events doesn’t have to be a lot of use, but it’s certainly a very useful function point. Hope this article can help you.