I would like to have a WebView page callback to my Android app. I have successfully done this using vanilla HTML page. When I add the same JavaScript code to my VueJS app, it does not want to build, and gives me an ELIFECYCLE error.
In my Kotlin code, I have the following:
binding.webView.addJavascriptInterface(JavaScriptInterface(), "Android")
private inner class JavaScriptInterface {
fun showWallet(string: String) {
AppPreference.PREFERENCE_SCREEN_TYPE = Constants.SCREEN_USER_DASHBOARD
mainActivity.getNavController().navigate(R.id.action_placeOrder_to_userDashboardFragment)
}
}
Android is my JavaScript object I've declared, and the function is showWallet, which navigates the user to another fragment.
In my VueJS code, I have the following:
let devicedUsed = this.$store.state.devicedUsed;
if (devicedUsed === 'apple'){
window.webkit.messageHandlers.message.postMessage('backToWallet');
}else{ //is Android device
Android.showWallet('backToWallet');
}
The above callback works without any issues in vanilla HTML. My problem is the above code does not build, and I get an error that Android is not defined. It seems VueJS is looking for Android, which is only defined in the Kotlin code.
How do I get around this problem?
Use the window. prefix to tell the compiler that it's a global variable:
// BEFORE:
Android.showWallet('backToWallet')
// AFTER:
window.Android.showWallet('backToWallet')
Related
I'm adding Raygun.io APM to our Angular 8 app with Angular Universal.
It is known that raygun.io has a client side javascript library and to add this to a Angular with Universal, DOM window API must be created. This can be done using domino npm using this code below:
There is also an installation guide for Angular via npm called raygun4js however the problem still exists.
// Domino for defining Windows API in SSR
(found # https://www.npmjs.com/package/domino )
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(index.html).toString();
const win = domino.createWindow(template);
global['window'] = win; // will be used for NodeJS to read Window API
global['document'] = win.document;
*domino creates a window api and sets it to a global called win.
After adding this line to an NPM project server.ts, build and run command - an exception is found:
Raygun.Utilities = raygunUtilityFactory(window, Raygun);
^
ReferenceError: raygunUtilityFactory is not defined
This roots that a raygunUtilityFactory function is not defined within window API. Looking inside raygun.js in Github
window.raygunUtilityFactory = function(window, Raygun) {
var rg = {
getUuid: function() {
function _p8(s) {
var p = (Math.random().toString(16) + '000000000').substr(2, 8);
return s ? '-' + p.substr(0, 4) + '-' + p.substr(4, 4) : p;
}
// more code.....
Question is, how can NodeJS read raygunUtilityFactory function during build if it can't find it in window API?
UPDATE: I tried to do this on a smaller project but it seems that even its document for installing raygun.io doesn't include procedures for Angular Universal. It basically can't detect window API using domino
Raygun.Utilities = raygunUtilityFactory(window, Raygun);
^
ReferenceError: raygunUtilityFactory is not defined
Answer: Setting Raygun js as a global object and referencing it to a declared variable inside a service.
Reference: https://hackernoon.com/how-to-use-javascript-libraries-in-angular-2-apps-ff274ba601af
declare var rg4js: any;
*place this inside your service or your main component ts.
<script type="text/javascript">
!function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){
(a[e].o=a[e].o||[]).push(arguments)},f=b.createElement(c),g=b.getElementsByTagName(c)[0],
f.async=1,f.src=d,g.parentNode.insertBefore(f,g),h=a.onerror,a.onerror=function(b,c,d,f,g){
h&&h(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[],a[e].q.push({
e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js");
</script>
*add this to your index.html or download and add it to your project.
Do take note that the raygun script should be referenced as rg4js.
Angular will automatically know that the rg4js inside your TS file is reference to your raygun script tag.
-- I'm now able to see the crash reporting and the pulse monitoring inside our client dashboard. However, I noticed that all client side errors logs are not caught. I'm still researching way to send these unhandled errors - starting with windows.onerror.
Good to hear you have figured out part of the solution!
AngularJS captures a lot of errors under the hood automatically and to properly capture errors you will need to register your own angular error handler and when the callback is fired you can use the Raygun4JS send method to send the message to Raygun.
export class RaygunErrorHandler implements ErrorHandler {
handleError(e: any) {
rg4js('send', {
error: e,
});
}
}
Raygun has a little bit of angular documentation but can't import raygun4js via npm for Angular Universal (as per your discovery) so you will need to modify the examples shown. That said they should provide a good starting point.
Recently I'm rewriting a plan JS app with Angular 6.
The old stuff work like this:
file js inside a iframe that call "parent.update_track()" function defined into main.js file into iframe container.
that's not work in Angular when app run on production, the response is: TypeError: parent.update_track is not a function.
so how can I call this function "update_track()" declared into a ts file in a component from an external js file contained into iFrame?
You can use post message functionality.
So in your iframe you can just call the opener window (the parent) to post message, for more info you can use this reference
Then in the angular app (the parent) you can use the following listener:
#HostListener('window:message',['$event'])
onMessage(e) {
if (e.origin!="http://localhost:4200") {
return false;
}
alert('here i am');
}
}
Note: you should change the e.origin test to support both local env and production.
I am currently trying to port an existing Android application that we have to an IOS app. The issue here is that a large portion of our Android app was made using a lot of webviews with custom Javascript code called from Android.
If anyone is not familiar with it, the code for android would go something like this:
mWebView.addJavascriptInterface(WebInterface(this), "Android");
// more settings or whatever
mWebView.loadUrl("yourUrl.html.whatever");
Then the interface to the Javascript code reads like this:
public class WebInterface {
Context mContext;
public WebInterface(Context c) { this.mContext = c;}
#JavascriptInterface
public String returnMessage() {
Toast.makeText(mContext, "This is being called from the interface", Toast.LENGTH_SHORT).show();
return "This is being called from the interface";
}
}
At this point any time we want to access the code for the returnMessage() method inside of the webview we simply call:
var getContentsFromIntefrace = Android.returnMessage()
// getcontentsFromInterface is now "This is being called from the interface"
These targeted calls(Android.returnMessage()) allow me to call the interface code only when I need to do so, in contrast, if I was to use Android APIĀ“s evaluate javascript methods I would have to watch for the code executing all over the place e.g if my evaluatejavascript Android API method sets a variable to something it will do so in any other part of the code where said variable exist(please do correct me if I am wrong, it seems fuzzy at first but it is an issue thus far)
===== The issue I am having with IOS
The above is merely an explanation of what I need to do, to show what I am attempting on IOS I will demonstrate the code:
In IOS if I want to se functions that call on swift code inside my controller I have to set it in JS as:
function testButton() {
webkit.messageHandlers.callbackHandlerTestSend.postMessage({action: "testButtonAction", data: {name: "E.A.P"}});
}
The above sends a named message('callbackHandlerTestSend') with the key value pairs of action and data which can be parsed on the appropriate swift delegate as:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if (message.name == "callbackHandlerTestSend") {
guard let body = message.body as? [String:Any] else {
print("Unable to do connection")
return
}
for(key, value) in body {
print("\(key) : \(value)")
if(key == "action"){
if(value as! String == "testButtonAction") {
intCounter += 1
print("The testButtonAction message action has been called")
webView.evaluateJavaScript("receiverP.innerHTML = 'It works \(intCounter)'", completionHandler: nil)
webView.evaluateJavaScript("receiverFunction('Message test')", completionHandler: nil)
// FIXME: evaluate JS might call on any other instance of receiverP, this is an issue
}
}
}
}// body of if
}// end of delegate
This is currently the only way in which I have been able to send Swift code back to the webview, by injecting the Javascript code with webView.evaluateJavascript()
My problem is that I need to capture everything with the whole:
webkit.messageHandlers.callbackHandlerTestSend.postMessage({action: "testButtonAction", data: {name: "E.A.P"}});
And it becomes bothersome to continue to do it as well as to have to individually parse each call for the proper parameters to be set while at the same time trying to not inject javascript code without messing something else up. So calling javascript and sending javascript code seems extremely cumbersome at this point and I would like to know if there is something I could do for the code to look more like the Android counterpart.
Any advice on what cool external libs out there I could use or in how could I modify the code to this would be greatly appreciated, I have been trying to find something in the IOS docs but have not found anything that I could use.
-- Edit:
At this time I am seriously considering an alternative such as React Native since it seems as the fastest route to develop an application that requires heavy usage of web like content as well as having limited knowledge of IOS in contrast to Android. I would still want to know if there is a viable solution to the above.
I have a similar problem to the person in this post; I'm trying to extend the cefsimple.exe app included with the chromium embedded framework binaries to include a V8 handler. I implemented the OnContextCreated() method and made sure to extend RenderProcessHandler in the SimpleHandler class. I'm trying to implement a simple window bound variable called test_string; here's what my code looks like;
void SimpleHandler::OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> object = context->GetGlobal();
object->SetValue("test_string", CefV8Value::CreateString("this is a test"), V8_PROPERTY_ATTRIBUTE_NONE);
}
But the program never arrives at any breakpoints I add within the method, and the variable is undefined on any webpages I load within the app. I saw that one of the solutions in the other thread is to enable the settings.single_process flag, which i've done, but my code still doesn't reach the breakpoint.
To be clear, I'm accessing the variable on pages with window.test_string.
Make sure that you are sending that CefApp to CefExecuteProcess.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
Found this solution here
Have you read through the General Usage guide? Some key points below
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-cefapp
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-processes
The single_process mode is not supported so I've never used it. In general I'd avoid it. The multi process architecture means you need to attach the debugger to the process. The Chromium guide is relevant to CEF in this instance.
https://www.chromium.org/developers/how-tos/debugging-on-windows#TOC-Attaching-to-the-renderer
you need to ensure your App is derived from CefRenderProcessHandler
not SimpleHandler!!!
class SimpleApp : public CefApp
, public CefRenderProcessHandler
{
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE;
valdemar-rudolfovich says you need to pass instance of SimpleApp in
CefExecuteProcess
When I try to use the autosuggestion in Webstorm(V 10.0.4/ Linux machine)
with the Revealing-Module-Pattern and the definition of the module is in one File like this:
var testModule = testModule || (function(){
function myPrivateTestFunction(){
console.log("test");
}
return{
test: myPrivateTestFunction
}
})();
in another File I try to call the the function by:
testModule.test();
it correctly finds the module-object, defined in the other file but doesn't find the function.
If I look at the settings: File->Settings->Javascript
There is an option called "Weaker type guess for completion".
If I enable this, it indeed shows my desired function testModule.test().
But it also shows all private members of the module and of all other modules, defined somewhere, so this doesn't make sense to me.
Logged as WEB-18186, please vote for it to be notified on updates
The feature was implemented by the Webstorm Team.
I tested it (in the Early Access Program Version 142.5255).
It works perfectly!
Thanks to the Webstorm-Team who implemented the feature that fast and to lena who created the ticket!