14

How can a native module callback to a JavaScript internal bridge method, then do processing in the internal method, for example parsing data into JSON, and then raise an event that the react native app receives?

For cross-platform bridge modules, I want to avoid duplicating code in both Objective C and Java. When possible, I want to write cross-platform bridge module code in JavaScript and reuse for Android and iOS.

My Current Solution:

This works.

This will result in two calls to sendEventWithName:

  1. sendEventWithName with the "_processResponseInJavaScriptMethod"
  2. forwardEventWithName to sendEventWithName to the react native app.

Question:

Is there an approach for native code to process data synchronously using a JS call, then send results immediately with sendEventWithName?

Or does every native > JS call require an async _bridge enqueueJSCall?

File: MyNativeModule.m

// Use forwardEventWithName to forward events 
// processed by common cross-platform JS to the react native app
RCT_EXPORT_METHOD(forwardEventWithName:(NSString*)name
                  body:(NSString*)body) {
    [self sendEventWithName:name body:body];
}

// 1 - react native app calls sendQueryToBluetoothDevice
// 2 - device calls commandResponse when response received
RCT_EXPORT_METHOD(sendQueryToBluetoothDevice:(NSString*)command {
    [_device sendCommand:command];
}

// 1 - Receives XML response from external Bluetooth device
// 2 - Sends XML to internal JS method for processing
// 3 - Internal JS method uses forwardEventWithName 
//     to send event to react native app
- (void) commandResponse:(NSString *) xml {    
    [self sendEventWithName:@"_processResponseInJavaScriptMethod"
                       body:@{@"xml": configuration}];

}

File: index.js ( native module )

 // Parse xml in common JS code for both Android and iOS native modules
emitter.addListener("_processResponseInJavaScriptMethod", (e) => {
    const body = parseXml(e.xml);

    // ?? Is there a way to send event directly using JS to the react native app ??

    // Hack - Call native method forwardEventWithName to sendEventWithName to react native app.
    ///
    // This makes two _bridge enqueueJSCall's
    // 1 - One call to sendEventWithName "_myNativeModuleInternalMethod"
    // 2 - Second call to sendEventWithName "myNativeModuleEvent
    MyNativeModule.forwardEventWithName(
      'myNativeModuleEventResponseReceived', 
       JSON.stringify(body));
  }
})
Ed of the Mountain
  • 5,219
  • 4
  • 46
  • 54

2 Answers2

6

I would recommend doing some reading on the Batched Bridge in React Native.

Native -> JS

Essentially what you can do is define a module with static methods on the JS side which you would then register with the BatchedBridge (registerCallableModule) - this then enables you to directly call this method from the native side via RCTBridge().enqueueJSCall, where you would provide the moduleDotMethod that corresponds to your module and function respectively; you would also pass a unique (or incremental) identifier that you would later use to map the response to each request.

JS -> Native

Next you would create a Native Module that would be called on the JS side to send data back to native, passing the response data and identifier which you would then handle on the native side.

I've worked on projects where the entire service layer is on the JS side and have used this approach successfully.

sooper
  • 5,991
  • 6
  • 40
  • 65
  • 1
    Thank you. Can you please comment on "My Current Solution:" added to the original question? Is there an approach for native code to process data synchronously using a JS call, then send results immediately with sendEventWithName? Or is RCTBridge().enqueueJSCall the only way? – Ed of the Mountain Jun 01 '17 at 15:09
  • 1
    Nothing across the bridge is synchronous - the closest you can get to synchronous calls is to use `constantsToExport` on the module, but these values are defined at runtime and cannot be changed. You can if you want to use the event dispatcher but that would require you to setup a listener on the JS side. – sooper Jun 01 '17 at 16:49
  • Since nothing is synchronous, it sounds like my "My Current Solution:" posted above is about as good as it gets. For future cross-platform reusability I may consider C++ wrapped in a *.mm file for iOS and a wrapped in JNI for Android. I had been using Qt 5.6 for iOS and Android which is C++ environment which worked pretty well. – Ed of the Mountain Jun 01 '17 at 18:06
5

Not sure what you mean by Internal bridge module Java Script method, but JS code can pass data to native via methods, and native can return data to JS via callbacks or promises. Native code can also send events to JS.

JS       --->     Native   // Method calls
Native   --->     JS       // Callbacks, Promises, Events

Refer the native module documentation for more details: - https://facebook.github.io/react-native/docs/native-modules-ios.html - https://facebook.github.io/react-native/docs/native-modules-android.html

satya164
  • 9,464
  • 2
  • 31
  • 42
  • 1
    I have the native module working. I just find myself duplicating code in Java and Objective C that could be re-used. Maybe I addListener in the beridge module's index.js and then receive callback from native code, process data, then emit event? react-native-fetch-blob appears it may be doing something like that? https://github.com/wkh237/react-native-fetch-blob – Ed of the Mountain May 31 '17 at 18:19
  • How you structure it depends on you. You have ways for native to send info to JS and JS to send info to native. – satya164 Jun 01 '17 at 03:03
  • Thank you. Is there an approach for native code to process data synchronously using a JS call, then send results immediately with sendEventWithName? – Ed of the Mountain Jun 01 '17 at 15:23
  • No, you cannot transfer data synchronously between JS and Native. – satya164 Jun 02 '17 at 04:35