After upgraded to 0.41, many modules don't work and get this error:
java.lang.RuntimeException: Illegal callback invocation from native module. This callback type only permits a single invocation from native code.
at com.facebook.react.bridge.CallbackImpl.invoke(CallbackImpl.java:32)
I think it's caused by this change in react-native which limits the callback to be invoked for only once:
Fail-Fast on Redundant Java Callback Invocations
@Override
public void invoke(Object... args) {
+ if (mInvoked) {
+ throw new RuntimeException("Illegal callback invocation from native "+
+ "module. This callback type only permits a single invocation from "+
+ "native code.");
+ }
mCatalystInstance.invokeCallback(mExecutorToken, mCallbackId, Arguments.fromJavaArgs(args));
+ mInvoked = true;
}
However, many modules would expect their callbacks to be invoked more than once. E.g. react-native-dialogs which shows native dialogs for Android. And on the dialog, we will probably invoke the callback for multiple times, e.g. select an option, press ok will both invoke the callback with different args.
Have we considered that sometimes we do need multiple callback invocations?
This appears to be the same issue as https://github.com/facebook/react-native/issues/12729. What's the answer guys?
Ok, here is the answer for now: Whenever you invoke a method from the javascript side on the module that may in turn invoke the callback you need to reset the callback:
resetCallback(){
MyModule.setTheCallback((value) => {
this.setState({ value: value });
});
}
invokeMethodThatMayInvokeCallback(){
resetCallback();
MyModule.bar(); // the method within the module that will invoke the callback.
}
@gavinyeah did you find any solution on this? I have the same problem on a single options dialog. The second time I try to select an option the app crash. Is this work around from @dobrienlzd working with the react-native-dialogs lib? If it's so I can't figure out how.
EDIT: seems like setting alwaysCallSingleChoiceCallback to false will solve my particular issue with the lib
Facing the same issue
I'm with this issue also
Instead of using callbacks, use events with RCTEventEmitter
Example below using swift. I left out the .m since it isn't relevant to event emitting with swift. The obj-c version is about the same and will be easy for you to google/figure out
edit: I put together a small blog post to explain this: https://medium.com/nycdev/calling-a-callback-multiple-times-in-a-react-native-module-5c3c61f2fca4
.swift
@objc(RNSpeechToText)
class RNSpeechToText: RCTEventEmitter {
var speechToText: SpeechToText?
var audioPlayer = AVAudioPlayer()
var hasListeners = false
static let sharedInstance = RNSpeechToText()
private override init() {}
override func supportedEvents() -> [String]! {
return ["StreamingText"]
}
@objc func initialize(_ username: String, password: String) -> Void {
speechToText = SpeechToText(username: username, password: password)
}
@objc func startStreaming(_ errorCallback: @escaping RCTResponseSenderBlock) {
var settings = RecognitionSettings(contentType: .opus)
settings.interimResults = true
let failure = { (error: Error) in errorCallback([error]) }
speechToText?.recognizeMicrophone(settings: settings, failure: failure) { results in
if(self.hasListeners)
{
self.sendEvent(withName: "StreamingText", body: results.bestTranscript)
}
}
}
@objc func stopStreaming() {
speechToText?.stopRecognizeMicrophone()
}
override func startObserving()
{
hasListeners = true
}
override func stopObserving()
{
hasListeners = false
}
}
.js
import { NativeEventEmitter, NativeModules } from 'react-native';
let {
RNSpeechToText
} = NativeModules
module.exports = {
SpeechToText: {
speechToTextEmitter: new NativeEventEmitter(RNSpeechToText),
initialize: function ( username, password )
{
RNSpeechToText.initialize( username, password );
},
startStreaming(callback)
{
this.subscription = this.speechToTextEmitter.addListener(
'StreamingText',
(text) => callback(null, text)
);
RNSpeechToText.startStreaming(callback)
},
stopStreaming()
{
this.subscription.remove()
RNSpeechToText.stopStreaming()
}
}
}
@pwcremin I tried following this method to fix this issue and having some serious issues getting NativeEventEmitter to work
Our events are not being registered and are receiving this error message in xcode: 'TestEvent' is not a supported event type for RCTEventEmitter. Supported events are: '(null)'
We have reviewed the documentation on how to implement a NativeEventEmitter and I believe we are following it pretty closely. The problem I think is with Objective-C and how we are including our EventManager class, the JavaScript side of this is really simple.
// SpotifyLoginViewController.m
#import "SpotifyLoginViewController.h"
#import "AppDelegate.h"
#import "SpotifyAuth.h"
@interface SpotifyLoginViewController () <WKNavigationDelegate>
@property (strong, nonatomic) WKWebView *webView;
@property(nonatomic) NSURL *login;
@end
@implementation SpotifyLoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
_webView = [[WKWebView alloc] initWithFrame:self.view.frame
configuration:configuration];
_webView.navigationDelegate = self;
_webView.allowsBackForwardNavigationGestures = true;
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[_webView loadRequest:[NSURLRequest requestWithURL:_login]];
self.view = _webView;
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
id<RCTBridgeDelegate> module = [[EventManager alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:module launchOptions:nil];
SpotifyAuth *sharedManager = [SpotifyAuth sharedManager];
NSURL *url = navigationAction.request.URL;
NSString *myScheme = [[sharedManager myScheme] stringByReplacingOccurrencesOfString:@"://callback" withString:@""];
if ([url.scheme isEqualToString:myScheme]) {
RCTEventEmitter *event = [[RCTEventEmitter alloc] init];
event.bridge = bridge;
[event sendEventWithName:@"TestEvent" body:@{@"name":@"metallica"}];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
// EventManager.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface EventManager : RCTEventEmitter <RCTBridgeModule>
- (NSURL*)sourceURLForBridge:(RCTBridge*)bridge;
@end
// EventManager.m
#import "EventManager.h"
#import <React/RCTBundleURLProvider.h>
@implementation EventManager
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
return @[@"TestEvent"];
}
- (NSURL*)sourceURLForBridge:(RCTBridge*)bridge {
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
return jsCodeLocation; // [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
}
@end
// player.js
import { NativeModules, NativeEventEmitter } from 'react-native';
const eventManager = new NativeEventEmitter(NativeModules.EventManager);
subscription = eventManager.addListener('TestEvent', (data) => {
console.log('the event fired!');
console.log(data);
});
We are using RN 0.44.0
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.
Hi there, still happening on 0.52.1.
As says in the docs:
A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later.
Most helpful comment
Instead of using callbacks, use events with RCTEventEmitter
Example below using swift. I left out the .m since it isn't relevant to event emitting with swift. The obj-c version is about the same and will be easy for you to google/figure out
edit: I put together a small blog post to explain this: https://medium.com/nycdev/calling-a-callback-multiple-times-in-a-react-native-module-5c3c61f2fca4
.swift
.js