The code is here

Many times we need to send events from native to JS. For example, a calendar event mentioned in the official document. You have scheduled a meeting or an event, and then it will happen on a specified date. Or turn off the contribution bike, and Bluetooth receives the signal that the lock is successful. Or for apps like geofences, when you enter/leave a geofence, you need to send events natively to JS.

First is a simple example

Calling a native method to set the native time of a delayed trigger is similar to calling the native setTimeout . After the time is up, an event will be sent from native to JS.

First, there will be a text box on the UI where you can enter the time, after the user enters the time and clicks the OK button. App will call the native method to execute the native setTimeout method.

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

An event listener will be registered in the JS part, and the JS code will be executed as soon as the native event is received. In this example, only a log is output for simplicity.

Since events are received and sent from native, a native module is essential. Know very little about this part of the students to be the venue This is the iOS , this is Android's

一点需要更新的是,现在官方推荐在实现Android原生模块的时候使用`ReactContextBaseJavaModule`。主要是出于类型安全的考虑。

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
这里省略了模块头文件。

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

2 : So when implementing, you need to implement the method supportedEvents this class. It is to add the name of the event we want to send from the original. like:

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

3 : The try-catch in method sendEventInSeconds resolve and reject just to realize the promise of the JS part

4 : This part is equivalent to setTimeout :

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

The code [self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole", @"with": @"RN"}]; completes 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() {
        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() {
                    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);
    }
}

Note : The specific native steps are a bit more than iOS. Here are all omitted, if you need to refer to the Android native module part mentioned above.

1 : sendEventInSeconds used to receive the time information passed by JS, and start setTimeout Android here.
2 : The try-catch part is the same as that of iOS, cooperating with resolve and reject.
3 : Android’s setTimeout :

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

4 : method FillingEventHole.this.sendEvent("FillingHole", params); call implemented in the native module in setEvent way to send events.
5 : Event sending method:

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

Front end implementation

Before starting the front-end part, let me say a few nonsense.

The native modules implemented by Android and iOS must have the same name. The module name must have the same name, and the native method must have the same name. When calling native methods on the front end, you need to ensure that the types are corresponding. For example, the public void sendEventInSeconds(long seconds, Promise promise) above example is of digital type, so when called by JS, it must be of digital type, otherwise an error will occur. Or, you can provide a custom type conversion method.

On the front end 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 () => {
      // 4
      listenersRef.current?.remove();
    };
  });

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

  return (
    //...
  );
}

1 : Get the defined native module NativeModules
2 : Use the defined native module to initialize NativeEventEmiotter
3 : Add a listener for native events
4 : Destroy the listener at the end
5 : When the user clicks the button, the method exposed by the native module is called to setTimeout method of the native event.

3> and 4> are the content of react hooks, students who are not familiar with it can refer to the official website.

There is also a practical tip here, using useRef save the listener of component events.

finally

After the preparations are done, you can start running. Shake the phone to turn on the debug mode to view the events received from the native.

There may not be many places used to emit events natively, but it is definitely a very useful feature. Hope this article can help you.


小红星闪啊闪
914 声望1.9k 粉丝

时不我待