Send event to js from swift or objective-c - javascript

I have created the following class (condensed version), heres a reference to the full file
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/ios/CastRemoteNative/NativeMethods.swift
#objc(NativeMethods)
class NativeMethods: RCTEventEmitter {
#objc(sendEventToJSFromJS)
func sendEventToJSFromJS {
self.emitEvent(eventName: "test", body: "bodyTestString")
}
func emitEvent(eventName: String: body: Any) {
self.sendEvent(withName: eventName, body: body)
}
}
This works perfectly and fires my callback listener that is in my javascript code when I call the emitEvent method like the following, its an altered snippet from
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/js/Components/ChromecastDevicesModal.js
From the javascript side
import {
NativeModules,
NativeEventEmitter
} from 'react-native'
//here I bring in the swift class to use inside javascript
var NativeMethods = NativeModules.NativeMethods;
//create an event emitter to use to listen for the native events when they occur
this.eventEmitter = new NativeEventEmitter(NativeMethods);
//listen for the event once it sends
this.subscription = this.eventEmitter.addListener('test', (body) => { console.log('in test event listener callback', body)});
NativeMethods.sendEventToJSFromJS() //call the native method written in swift
I simply have the sendEventToJSFromJS method invoked on a button press in javascript
Again, this works and the console.log('in test event listener callback', body) code works and runs on the javascript side
My Issue where this does NOT work:
If I was to do the following inside the swift file after defining the class, this would not work:
var nativeMethodsInstance = nativeMethods()
nativeMethodsInstance.sendEventToJSFromSwift()
Why? Because the following error is thrown:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'bridge is not set. This is probably because you've explicitly synthesized the bridge in NativeMethods, even though it's inherited from RCTEventEmitter.'
So, when creating an instance of NativeMethods, versus not... what is the difference?
For additional information:
Objective-C gets the same bridge not set issue when I write these same snippets of code in .h and .m files instead of in .swift files
I found where the error message is getting printed in the native code, but it just has the variable
_bridge
and is checking to see if it is nil
The files are this error comes from is:
RCTEventEmitter.h
RCTEventEmitter.c
here is the full snippet of RCTEventEmitter.c
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
RCTAssert(_bridge != nil, #"bridge is not set. This is probably because you've "
"explicitly synthesized the bridge in %#, even though it's inherited "
"from RCTEventEmitter.", [self class]);
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(#"`%#` is not a supported event type for %#. Supported events are: `%#`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:#"`, `"]);
}
if (_listenerCount > 0) {
[_bridge enqueueJSCall:#"RCTDeviceEventEmitter"
method:#"emit"
args:body ? #[eventName, body] : #[eventName]
completion:NULL];
} else {
RCTLogWarn(#"Sending `%#` with no listeners registered.", eventName);
}
}
Where does this _bridge value get set and how does it get set so I can know, in the cases where it is failing how to set it
I found the following also in RCTEventEmitter.h
#property (nonatomic, weak) RCTBridge *bridge;
In the error that is given it mentions the bridge is inherited in the RCTEventEmitter, so is this maybe an issue with the weak part to the bridge property?
Or do I need to change my strategy in how I'm doing this all together?
I know it probably has to be something to do with me not fully understanding the
#synthesize bridge = _bridge;
part of the code and all the languages being mixed in doesnt help much lol...
This is really hard, so any help would be much appreciated!
Thanks so much for your time
here is a link to the full project when the project history code represented the code from my question above (since I have since made changes to the project):
https://github.com/cotyembry/CastRemoteNative/tree/7e74dbc56f037cc61241f6ece24a94d8c52abb32

I figured it out
Warning: this solution uses a deprecated method react native method - I could not figure out how to "properly" inherit from the RCTEventEmitter and send an event... every time I tried to the _bridge would end up being nil
Make sure Swift is bridged to Objective C (if you're using swift to send the event to javascript)
Do Not create instances of the exported Native modules (whether they be written in Swift or Objective C)
Let React Native's underlying implementation do this and for each and every class that needs to send an event, export that particular Native Class Objective C Implementation code or Swift code (the Native Module) to React-Native. This allows the javascript to be able to listen to the event
var publicBridgeHelperInstance = PublicBridgeHelper() //instantiate the the objective c class from inside the .swift file to use later when needing to get a reference to the bridge to send an event to javascript written in react native
#objc(DeviceManager) //export swift module to objective c
class DeviceManager: NSObject {
#objc(deviceDidComeOnline:) //expose the function to objective c
public func deviceDidComeOnline(_ device: GCKDevice) {
//imagine this deviceDidComeOnline function gets called from something from the Native code (totally independent of javascript) - honestly this could be called from a native button click as well just to test it works...
//emit an event to a javascript function that is a in react native Component listening for the event like so:
//1. get a reference to the bridge to send an event through from Native to Javascript in React Native (here is where my custom code comes in to get this to actually work)
let rnBridge = publicBridgeHelperInstance.getBridge() //this gets the bridge that is stored in the AppDelegate.m file that was set from the `rootView.bridge` variable (more on this later)
//(if you want to print the bridge here to make sure it is not `nil` go ahead:
print("rnBridge = \(rnBridge)")
//2. actually send the event through the eventDispatcher
rnBridge?.eventDispatcher().sendAppEvent(withName: "test", body: "testBody data!!!")
}
}
in AppDelegate.h put (additionally to the code that was already in the file)
#import "YourProjectsBridgingHeaderToMakeThisCodeAvailableInSwift.h" //replace this with your actual header you created when creating a swift file (google it if you dont know how to bridge swift to objective c)
#interface PublicBridgeHelper: NSObject
-(RCTBridge*)getBridge;
#end
in AppDelegate.m put (in addition to the code that was already in the file)
#import <React/RCTRootView.h>
RCTBridge *rnBridgeFromRootView;
#implementation PublicBridgeHelper //this is created to SIMPLY return rnBridgeFromRootView defined above over to my Swift class when actually sending the event to javascript that defines a react native Component
-(RCTBridge*)getBridge {
NSLog(#"rnBridgeFromRootView = #%#", rnBridgeFromRootView);
return rnBridgeFromRootView;
}
important - also make sure to add the following line of code to the Objective C .h's bridging header to make this PublicBridgeHelper definition available to be used in the .swift code
#import "AppDelegate.h"
finally,
now to show you how to set the rnBridgeFromRootView variable used in AppDelegate.m (that gets returned and used in the .swift code right before sending the event to javascript)
open AppDelegate.m and in the method body of
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... }
include the following after the line of code that instantiates the rootView variable
i.e. after the line that probably looks like
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:#"YourProjecNameProbably" initialProperties:nil launchOptions:launchOptions];
add:
rnBridgeFromRootView = rootView.bridge //set the bridge to be exposed and returned later and used by the swift class
Now to explain the publicBridgeHelperInstance.getBridge() part that is in the .swift file
publicBridgeHelper is an instance of an objective c class which allows the swift class ability to get a reference to the react native bridge
If you are still having problems understanding my answer after reading this I made a video over it and you can watch it here:
https://www.youtube.com/watch?v=GZj-Vm9cQIg&t=9s

Related

How do I listen custom js window events in dart 2.15 / flutter 2.8

I'm using flutter for web. I want to process custom event in my flutter javascript code. I open popup window from flutter:
import 'dart:js' as js;
js.context.callMethod('open', ['http://localhost:8083/popup-page.html?params', 'loginWindow', 'width=600,height=600,left=600,top=200']);
popup-page.html is part of my flutter web application. During its lifecycle popup-page.html is reloaded several times so I can't just assign my callback function to some specific dom.window object. So in popup-page.html I have the following javascript to call back my flutter code when needed:
opener.parent.dispatchEvent(new CustomEvent('myCallback', {detail: callbackDetailsObj}));
Now I want to get and process callbackDetailsObj in my flutter app.
My initial idea was to use window.addEventListener('myCallback', callback) or window.on['myCallback'].listen(callback) from dart:html package. In this and this examples people are just using CustomEvent as callback function arg and casting event.detail to custom dart class. But for me that doesn't work. First - in my callback I get instance of JavaScriptObject for event arg. That JavaScriptObject can't be cast to either CustomEvent or event Event (despite the fact EventListener arg is typedef EventListener(Event event)). So I was not able to cast or convert JavaScriptObject to anything useful (including JsObject from dart:js package) and also was not able to get any fields of that object - e.g. type, target, details.
So in order to create working solution I used context.callMethod + allowInterop:
import 'dart:js' as js;
void addListener() {
var callbackPopupJs = js.allowInterop(callbackPopup);
js.JsObject options = js.JsObject.jsify({'once': true});
js.context.callMethod('addEventListener', ['myCallback', callbackPopupJs, options]);
}
void callbackPopup(js.JsObject ev) {
developer.log('Browser Callback');
developer.log('Event type: ${ev['type']}, runtime: ${ev.runtimeType}, target: ${ev['target']}');
developer.log('Detail : ${ev['detail']}');
}
And now I'm able to get CustomEvent.detail. But I'm not if this method is optimal and it's not possible to use window.addEventListener() and CustomEvent to process window callbacks.

ActiveX / COM object event handler for Hardware Device

I am creating an ActiveX control for a hardware (card swiper) device. I have understanding of ActiveX development and working, but I am little stuck with my specific scenario. Device SKD (dll files) have method to activate swiper/chip reader in order to be in listening state. Assume the following:
SDK sdk = new SDK();
sdk.RegisterCallback(GlobalCallback);
sdk.ActivateSwiper();
public void GlobalCallback(op, .., ..)
{
switch(op) {
case Swiped:
readCardData();
break;
case TransactionData:
readData();
break;
//. . . .
}
Above code is just for example. SDK has a global callback method for any event triggered through device (eg, card swiped, ICC seated, etc). If the SDK is being used in WPF/WinForm app, it is super easy get it working. But in my case, I must need to handle the raised event in javascript on my web page.
So, I have exposed a method in ActiveX to turn on the device, which works perfect:
[Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
[ComVisible(true)]
public string ActivateDevice()
{
SDK sdk = new SDK();
sdk.RegisterCallback(GlobalCallback);
string resultCode = sdk.ActivateSwiper();
return resultCode;
}
//. . <other code> . . .
}
and use it in webpage as:
<object id="myComComponent" name="myComComponent" classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4"></object>
<script language="javascript" type="text/javascript">
var returnCode = myComComponent.ActivateDevice(); //or by creating ActiveXObject
</script
I have also got the idea about how to handle events using ActiveX on webpage. I learnt it from HERE. But the event is handled by calling the exposed method to raise event. And in my case, when card is swiped, the call will go to GlobalCallback() method.
Questioins:
Is there any workaround, I can implement handler in my scenario, to make it usable on javascript?
I am thinking of something like PropertyChanged event, bound to a string property, which holds the derived card data. and the handlers returns this property value. Is there any workaround possible like this? I need little help with this.
I am thinking this as:
public static string CARD_DATA;
public void GlobalCallback(op, .., ..)
{
switch(op) {
case Swiped:
CARD_DATA = readCardData();
//and CARD_DATA is bound to property-changed event, and its handler returns its value.
//other code
Is this even possible? If so, What to be exposed? and How to use it? as this property will be changed internally, when card is swiped, and case Swiped: is executed. Is there any workaround?
Other Info:
Web App is MVC 4 famework based
Device is The Augusta (IDTech). (they don't have actieX/plugin for web)

Qt function runJavaScript() does not execute JavaScript code

I am trying to implement the displaying of a web page in Qt. I chose to use the Qt WebEngine to achieve my task. Here's what I did :
Wrote a sample web page consisting of a empty form.
Wrote a JS file with just an API to create a radio button inside the form.
In my code, it looks like this :
View = new QWebEngineView(this);
// read the js file using qfile
file.open("path to jsFile");
myJsApi = file.Readall();
View->page()->runjavascript (myjsapi);
View->page()->runjavascript ("createRadioButton(\"button1\");");
I find that the runJavaScript() function has no effect on the web page. I can see the web page in the output window, but the radio button I expected is not present. What am I doing wrong?
I think you will have to connect the signal loadFinished(bool) of your page() to a slot, then execute runJavaScript() in this slot.
void yourClass::mainFunction()
{
View = new QWebEngineView(this);
connect( View->page(), SIGNAL(loadFinished(bool)), this, SLOT(slotForRunJS(bool)));
}
void yourClass::slotForRunJS(bool ok)
{
// read the js file using qfile
file.open("path to jsFile");
myJsApi = file.Readall();
View->page()->runJavaScript(myjsapi);
View->page()->runJavaScript("createRadioButton(\"button1\");");
}
I had this problem, runJavascript didn't have any effect. I had to put some html content into the view (with page().setHtml("") before running it.
Check the application output, it might contain JavaScript errors. Even if your JS code is valid, you might encounter the situation where the script is run before DOMContentLoaded event, that is document.readyState == 'loading'. Therefore, the DOM might not be available yet, as well as variables or functions provided by other scripts. If you depend on them for your code to run, when you detect this readyState, either wait for the event or try calling the function later, after a timeout. The second approach with timeout might be needed if you need to get the result of the code execution, as this can be done only synchronously.

webEngine.executeScript(); Throwing Exception

I'm writing a JavaFX App, which Interacts with the JavaScript, Using WebView and WebEngine (.executeScript() Method).
Here, I have this part of code from Medow.java, which loads map.html(Contains JavaScript Code), And This Code Works Pretty well:
add_button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ea5) {
// webEngine.executeScript("document.fun();"); // For Drawing Shapes
if (add == false) {
webEngine.executeScript("document.fun();"); // For Drawing Shapes
add = true;
}
// }
else {
webEngine.executeScript("document.reSet();"); // To remove Drawing Shapes
add = false;
}
}
});
In Here
webEngine.executeScript();
Is Invoking Appropriate JavaScript function's
But Now, I want my Java Code to Invoke Some JS function, when the Program Starts, So I'm directly writing :
webEngine.executeScript("document.draw();");
right Under/after the code, which loads the map.html file.
So, now as Both of the
webEngine.execute("document.fun();"); and webEngine.executeScript("document.draw();"); are nearly similar, I cannot Understand what Difference does, it makes to be inside the <button>.setOnAction block and to be outside it, Because both WebEngine and webView are declared as Global Variables.
cannot invoke document.draw() function using HTML's onLoad options, because i need to pass some Values To function draw from java.
The Exception Generated is :
netscape.javascript.JSException: TypeError: undefined is not a function (evaluating 'document.draw()')
how can i make this work? thank you
While Continuously trying to figure out whats the cause, I Discovered That the HTMLDocument Object, created using webEngine.load(), is for some reason visible only inside the handle method, And nowhere else, even though its been defined outside.
What happens here is that you want to call a JavaScript function before the content is loaded completely. Therefore the function draw is not defined. So the only way is to wait until the page is loaded. There are two ways that might prove useful:
Add a changelistener on the state and execute the JavaScript once the loading has succeeded:
String htmlURL = ...
webView.getEngine().load(htmlURL);
webView.getEngine().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State t1) {
if (t1 == Worker.State.SUCCEEDED) {
// this will be run as soon as WebView is initialized.
webView.getEngine().executeScript("document.draw()");
}
}
});
The other way is more of a solution within JavaScript. You first have to register a bridge between Java and your html page (has to be done in the SUCCEEDED state change as well, see WebView callback from Javascript):
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", this);
Now this JavaObject is referenced in your JavaScript. Let's say that you have a method on the class that is of the above type of this:
public void executeOnPageLoaded() {
...
}
Then you can call this method from Javascript. If you are using jQuery it could look like this:
$( document ).ready(function() {
console.log( "ready!" );
app.executeOnPageLoaded();
});
This second approach is more complex but in the long run may give you more flexibility.
When you start working with JavaScript in the WebView is is a good idea to have Firebug lite in there as well, so investigate what is happening but mainly to have a means to seed the console output of JavaScript. See Java FX application onload event

How can I attach an event handler to the process's exit in a native Node.js module?

I'm working on implementing correct memory management for a native Node.js module. I've ran into the problem described in this question:
node.js native addon - destructor of wrapped class doesn't run
The suggested solution is to bind the destructors of native objects to process.on('exit'), however the answer does not contain how to do that in a native module.
I've taken a brief look at the libuv docs as well, but they didn't contain anything useful in this regard, either.
NOTE: I'm not particularly interested in getting the process object, but I tried it that way:
auto globalObj = NanGetCurrentContext()->Global();
auto processObj = ::v8::Handle<::v8::Object>::Cast(globalObj->Get(NanNew<String>("process")));
auto processOnFunc = ::v8::Handle<::v8::Function>::Cast(processObj->Get(NanNew<String>("on")));
Handle<Value> processOnExitArgv[2] = { NanNew<String>("exit"), NanNew<FunctionTemplate>(onProcessExit)->GetFunction() };
processOnFunc->Call(processObj, 2, processOnExitArgv);
The problem then is that I get this message when trying to delete my object:
Assertion `persistent().IsNearDeath()' failed.
I also tried to use std::atexit and got the same assertion error.
So far, the best I could do is collecting stray ObjectWrap instances in an std::set and cleaning up the wrapped objects, but because of the above error, I was unable to clean up the wrappers themselves.
So, how can I do this properly?
I was also getting the "Assertion persistent().IsNearDeath()' failed" message.
There is a node::AtExit() function that runs just before Node.js shuts down - the equivalent of process.on('exit').
Pass a callback function to node::AtExit from within your add-on's init function (or where ever is appropriate).
The function is documented here:
https://nodejs.org/api/addons.html#addons_atexit_hooks
For example:
NAN_MODULE_INIT(my_exports)
{
// other exported stuff here
node::AtExit(my_cleanup);
}
NODE_MODULE(my_module, my_exports) //add-on exports
//call C++ dtors:
void my_cleanup()
{
delete my_object_ptr; //call object dtor, or other stuff that needs to be cleaned up here
}

Categories

Resources