JSInterface

warwound

Expert
Licensed User
Here is my first B4A library JSInterface.

JSInterface adds a new javascript interface to a WebView.
The new interface provides a bridge which allows your B4A code and any javascript (within a web page displayed in a WebView) to call each others' Subs/Functions.

Your B4A code can use the interface's execJS method to execute any javascript in the web page.
execJS returns no value to your B4A code.

And the web page javascript can use the interface's CallSub method to call a B4A Sub.
CallSub will return an empty string to your javascript if the B4A Sub returns nothing, otherwise CallSub will the Sub's return string to your javascript.

CallSub is an overloaded method - it can handle calling a B4A Sub with none, one or two string parameters.

Example javascript code:
B4X:
myJSNamespace.CallSub("myB4ASubName");
myJSNamespace.CallSub("myB4ASubName", "param 1");
myJSNamespace.CallSub("myB4ASubName", "param 1", "param 2");

I developed JSInterface so that i could save the state of a web page in a WebView and restore it when the device orientation changes but it has many other possibilities...

Martin.

[edit]Oooops forgot to say a big thanks to agraham for all his help![/edit]

JSInterface is no longer available or supported, please check out WebViewExtras.
 
Last edited:

agraham

Expert
Licensed User
Good thinking about overloading a single method name. I'm too used to thinking about naming methods being called from B4A which doesn't support overloading.

A couple of hopefully constructive comments. By convention Java package names "parse" from left to right, so a more conventional one would be "uk.co.martinpearman.b4a.jsinterface" although for B4A libraries it really doesn't matter what they are called as the outside world never sees them.

At the momenet your CallSub method appears in the IDE Intellisense and can be inserted into code. You can prevent that by using the @Hide annotation on the CallSub methods which will stop the IDE seeing them but still leave them public for Javascript.

You don't need to keep a WebView instance as you only access it in the Initialize method and doing so can cause a memory leak if your library instance is declared in Process_Globals. If you do need to keep a reference to a UI object then you should add the @ActivityObject annotation to your class then B4A won't let it be declared as a process global.
 

bluejay

Active Member
Licensed User
Excellent work warwound!

If this is your first library then I am extremely impressed.

A big thanks to both you and agraham as this was clearly an non-trivial piece of work and the result is a major boost to the functionality of B4A.

bluejay (aka U137a)
 

warwound

Expert
Licensed User
Hi all and thanks for the responses.

After a little trial and error i've updated the library and it's now a global process safe object.

I had to update the execJSmethod so that it requires a reference to a WebView instance when it is called:

B4X:
public void execJS(WebView aWebView, String jsStatement) {
   jsStatement = "javascript:" + jsStatement;
   aWebView.loadUrl(jsStatement);
}

Previously the library kept a reference to the WebView instance that the interface had been added to and used it in the ExecJS method to call the WebView LoadURL method, that why the old ExecJS method didn't need to be passed a WebView instance.
So the new method requires that the library keeps no reference to any activity object - the library can now be declared a process global.

What if you have more than one WebView in a single activity and you want a JSInterface added to each?
Is an instance of JSInterface required for each WebView?

B4X:
public class JSInterface {
   private BA activity;
   public void Initialize(BA aBA, WebView aWebView, String jsNamespace) {
      activity = aBA;
      aWebView.addJavascriptInterface(this, jsNamespace);
   }
   // rest of source omitted

Would adding the same instance of JSInterface to more than one instance of a WebView cause problems?
Would the new javascript interface have to be a static class?
Does the WebView addJavascriptInterface method accept a static class or would a static class have problems accessing non-static methods of other non-static classes such as LoadUrl?
My limited knowledge of Java is showing lol - i shall have to make time to do some tests.

I've taken agraham's advice and renamed the package and hidden the CallSub method from B4A.

What's left to do?

I want another overloaded CallSub method such as this:

B4X:
public String CallSub(String subName, String param1, Float param2) {
   subName = subName.toLowerCase();
   Object[] params = { param1, param2 };
   return (String) interfaceBA.raiseEvent(this, subName, params);
}

I could use this method to send a JSON property name/numeric value from javascript to my B4A app.
(I know everything can be done using strings but am keen to improve my Java as well as this library!)

In fact i've already tried adding and using this new method - it compiles fine but causes an error when trying to raise the event.
The params object causing a signature mismatch when it tries to call my B4A Sub:

B4X:
Sub setStoreData(property As String, value As Float)
   ' code here
End Sub

I'd previously tested to find what type a javascript numeric value has when passed to the interface with these methods:

B4X:
public String WhatType(Integer myInt){
   String str="int";
   return (String) interfaceBA.raiseEvent(this, "debugit", str);
}

public String WhatType(Float myFloat){
   String str="float";
   return (String) interfaceBA.raiseEvent(this, "debugit", str);
}
public String WhatType(Long myLong){
   String str="long";
   return (String) interfaceBA.raiseEvent(this, "debugit", str);
}

Where my B4A Sub debugit simply logged the value of the string passed to it:

B4X:
Sub debugit(str As String)
   Log(str)
End Sub

Now in my javascript i passed various numeric values to the overloaded WhatType method:

B4X:
B4A.WhatType(1);
B4A.WhatType(23.45);
B4A.WhatType(Math.pow(23,40);

No matter what numeric value i passed to the interface only the method overloaded with a Float parameter was ever called - so that's the type i used for my new overloaded CallSub method.
I think the problem is the params object being passed from the new overloaded CallSub method back to my B4A setStoreData method...

Updated library and example code attached to my original post.

Thanks.

Martin.
 

agraham

Expert
Licensed User
What if you have more than one WebView in a single activity and you want a JSInterface added to each?
Is an instance of JSInterface required for each WebView?
No, I don't think so. As long as you Initialize each WebView individually it should be OK as far as I can see. However, as each WebView runs Javascript on its own thread if twoWebViews try to call the same Sub you might get cross thread variable corruption problems. The CallSubs themselves are thread-safe as they hold only a local variable but if the called Subs access Globals then there might be a problem.

The params object causing a signature mismatch when it tries to call my B4A Sub:
That's puzzling! I can't see anything wrong there. I'd put a Log in each CallSub variant to see that Javascript really is calling the String-Float variant and not the String-String one.
B4X:
Log.i("B4A", "Callsub(S,F)");

P.S. Your version control seems weak! There's no @Version attribute in the library code.
EDIT:- My apologies - the version is in the jar but the attribute gets removed from the compiled code.
 
Last edited:

bluedude

Well-Known Member
Licensed User
Pretty awesome because that means we can use a Websocket and pass realtime messages to B4A.

Will try it out, could solve the push solution people are asking for.
 

wes58

Active Member
Licensed User
Yes, It's really great. Now I can use google maps Api 3 with markers placed in Webview with real live position tracking while driving/moving.
 

warwound

Expert
Licensed User
Hi again everyone.

I've updated JSInterface to version 1.2 and it now has a second interface that it can add to a WebView:

B4X:
public static void addConsoleInterface(WebView aWebView) {
   aWebView.setWebChromeClient(new WebChromeClient() {
      @Override
      public boolean onConsoleMessage(ConsoleMessage aConsoleMessage) {
         String logMessage = aConsoleMessage.message() + " in " + aConsoleMessage.sourceId() + " (Line: " + aConsoleMessage.lineNumber() + ")";
         if (aConsoleMessage.messageLevel() == ConsoleMessage.MessageLevel.DEBUG) {
            Log.d("B4A", logMessage);
         } else if (aConsoleMessage.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
            Log.e("B4A", logMessage);
         } else if (aConsoleMessage.messageLevel() == ConsoleMessage.MessageLevel.LOG) {
            Log.i("B4A", logMessage);
         } else if (aConsoleMessage.messageLevel() == ConsoleMessage.MessageLevel.TIP) {
            Log.v("B4A", logMessage);
         } else if (aConsoleMessage.messageLevel() == ConsoleMessage.MessageLevel.WARNING) {
            Log.w("B4A", logMessage);
         } else {
            // log all other message types with Log.i (info)
            // this condition currently will never occur but is included
            // for future compatibility
            Log.i("B4A", logMessage);
         }
         return true;
      }
   });
}

Call the addConsoleInterface method, passing an instance of a WebView.
All console and error messages from the WebView will be echoed to the Android log.

At last i can debug my javascript errors with a minimum of effort!

I've also updated how the library adds either interface to a WebView instance.
When you call either the addJSInterface or addConsoleInterface method, a new instance of the interface to be added is created.
You can create a single instance of JSInterface and use it to add either (or both) interfaces to more than one instance of a WebView.
JSInterface keeps no references to UI objects so can be declared as a process global or an actvity global.

I've also updated the documentation and example program.

Version 1.2 of JSInterface is attached to the first post in this thread.

[edit]
WebChromeClient's built-in onJsAlert method seems to work without being overriden and therefore adding the console interface will enable your web page to display alert messages - alert being a modal dialog box.
Documentation for WebChromeClient can be found here:
WebChromeClient | Android Developers
I imagine that onJsAlert is not the only built-in method that is available to a web page's javascript when the console is added.
[/edit]

Martin.
 
Last edited:

warwound

Expert
Licensed User
I have updated JSInterface to version 1.3.

The only change here is a new overloaded CallSub method which can handle three String parameters:

B4X:
myJSNamespace.CallSub("myB4ASubName", "param 1", "param 2", "param 3");

The updated library files are attached to the first post in this thread.

Martin.
 

neavilag

Member
Licensed User
Yes, It's really great. Now I can use google maps Api 3 with markers placed in Webview with real live position tracking while driving/moving.

Wes, you mean the map is a webview updated from GPS library ? Do you have an example ;-)

regards
 
Last edited:

warwound

Expert
Licensed User
Hi all.

I have just released a new library WebViewExtras:

http://www.basic4ppc.com/forum/addi...al-updates/12453-webviewextras.html#post70053

This new library is the successor to JSInterface and JSInterface will no longer be updated.

Existing users of JSInterface should be able to update to WebViewExtras with just a little code modification.

addJSInterface() is now addJavascriptInterface().

execJS() is now executeJavascript().

addConsoleInterface() is gone - replaced by addWebChromeClient().

Finally the interface method CallSub() has a new parameter callUIThread - check out the above link for more details.

Thanks.

Martin.
 

legion48

Member
Licensed User
How to install JSInterface libraries?

I get this when I try and run the GPS tutorial:

Compiling code. Error
Error parsing program.
Error description: Unknown type: webviewextras
Are you missing a library reference?
Occurred on line: 138
Dim MapViewerExtra As WebViewExtras


I downloaded the JSInterface Libraries and the install file said :

"To install the JSInterface library:
Move both JSInterface.jar and JSInterface.xml files to your B4A Additional Libraries folder."



Problem 1 - I don't have an "Additional Libraries" folder anywhere under the main B4A folder.

Problem 2 - I created an Additional libraries folder directly under the B4A folder and put the .jar and .xml files in the but still get the error message that is shown at the start of this post.

I'm sure that I'm missing an obvious step here:BangHead: Can someone tell me what it is please?
 

NJDude

Expert
Licensed User
Open the designer and on the top menu bar select: Tools -> Configure paths, then add the path to your "additional libs" in the corresponding box.
 

legion48

Member
Licensed User
Thnaks NJDude, I now see the JSinterface in the projec t but I still get:

Compiling code. Error
Error parsing program.
Error description: Unknown type: webviewextras
Are you missing a library reference?
Occurred on line: 138
Dim MapViewerExtra As WebViewExtras


when I press F5 to run/compile.

Should I be posting this in the GPS tutorial thread now?
 
Top