Monodroid JavaScript Interface - javascript

Monodroid does not yet natively support JavaScriptInterface with WebView.
I'm looking for an example .java file that can be used with this workaround.
IntPtr JavaScriptInterface_Class = JNIEnv.FindClass ("the/package/for/JavaScriptInterface");
IntPtr JavaScriptInterface_ctor = JNIEnv.GetMethodID (JavaScriptInterface_Class, "<init>", "()V");
IntPtr instance = JNIEnv.NewObject (JavaScriptInterface_Class, JavaScriptInterface_ctor);
appView.AddJavascriptInterface (new Java.Lang.Object (instance), "Android");

You could use a custom .java such as:
// TODO: use an actually valid package name. :-)
package the.package.for;
public class JavaScriptInterface {
// The JNI in the original question uses a default constructor.
// Either provide one explicitly or use the implicit one...
public JavaScriptInterface ()
{
}
// TODO: add any methods you want invokable from JavaScript here.
}
Don't forget to set the Build action for your .java file to to AndroidJavaSource.

I know that this thread is already a bit old. But i found this when was looking for the same and here is the solution how you can use c# methods
public class AndroidInterface : Java.Lang.Object
{
[Export]
public void Save(string text)
{
}
}
AndroidInterface androidInterace = new AndroidInterface();
webView.AddJavascriptInterface(androidInterface, "Android");

Related

unable to resolve method in class

1.is it possible to call the native method in the gwt into the other native method ?
this is the method i am calling from the VectorSource.java into Map.java
https://github.com/VOL3/v-ol3/blob/master/gwt-ol3/src/main/java/org/vaadin/gwtol3/client/source/VectorSource.java
public final native JsArray<Feature> getFeatures()/*-{
return this.getFeatures();
}-*/;
and i created a native method in Map.java class and getting the Features and i want to return these feature values to addOnPostRenderListener method below are the changes in the Map.java class
public native final Feature getFeatures(VectorSource sourceFeature)/*-{
var features=sourceFeature.#org.vaadin.gwtol3.client.source.VectorSource::getFeatures();
return features;
}-*/;
public native final void addOnPostRenderListener(OnPostRenderListener listener)/*-{
if(!this.__registered){
var that=this;
that.once('postrender',function(){
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
if(feature!=null){
var coordinate=feature.getGeometry().getCoordinate();
if(coordinates!=null){
var MapEvent={Pixel:that.getPixelFromCoordinate(that.__transformInputCoordinate(coordinates))};
that.__notifyPostRenderListeners(MapEvent);
}
}})
this.__postRenderListeners.push(listener);
}
}-*/;
the remaining code remains the same as shown in the below link
https://github.com/VOL3/v-ol3/blob/master/gwt-ol3/src/main/java/org/vaadin/gwtol3/client/Map.java
at the below code i am getting the error,as Expected a valid parameter type signature in JSNI method reference these lines of code is in the addOnPostRenderListener method
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
my target is to call the method getFeatures() from VectorSource.java class into the Map.java class and send the values to the other native method which is addOnPostRenderListener method.
Interface
public interface OnPostRenderListener {
public void onRender(MapEvent posEvent);
}
MapEvent
public class MapEvent extends JavaScriptObject {
protected MapEvent() {
}
public static final native Feature create()/*-{
return new $wnd.ol.Feature();
}-*/;
public native final Geometry getGeometry()-{
return this.getGeometry();
}-;*/
public native final Geometry getGeometry()/*-{
return this.geometry;
}-*/;
public native final Coordinate getCoordinate()/*-{
return this.coordinate;
}-*/;
public native final Pixel getPixel()/*-{
return this.Pixel;
}-*/;
//written code not used
public native final Map getPixelFromCoordinate(Coordinate coord)/*-{
return this.getPixelFromCoordinate(coord);
}-*/;
}
You need to pass your VectorSource sourceFeature parameter too. You are missing it in that.#org.vaadin.gwtol3.client.Map::getFeatures(Lcom/google/gwt/core/client/Source;);.
One way to do this, would be add it to your
addOnPostRenderListener(OnPostRenderListener listener)
e.g. addOnPostRenderListener(OnPostRenderListener listener, VectorSource vectorSource)
and then to access it like this:
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
Although I would recommend to completely drop JSNI and use the much better JsInterop. There is an OL-implementation using JsInterop too. Take a look here

GWT and JSInterop namespace

I've got some native JS test code here (mydialog.js)
var MyDialog = {
foo : function()
{
console.log("foo");
}
};
I'm injecting using the following code using GWT:
ScriptInjector.fromUrl("mydialog.js").setCallback(new Callback<Void, Exception>()
{
#Override
public void onFailure(Exception reason)
{
Window.alert("Dialog Injection Failed!" + reason);
}
#Override
public void onSuccess(Void result) {}
}).inject();
}
Then, I'm trying to set up a JSInterop class here:
#JsType(isNative=true, namespace=JsPackage.GLOBAL, name="MyDialog")
public class MyDialog
{
public static native void foo();
}
The problem, is that the namesoace JsPackage.GLOBAL isn't accurate. The injected code doesn't live under the global namespace, but rather the one generated by GWT and presumably inside the GWT iframe I believe. What is the namespace I need?
In other words, what should this be:
...namespace=???...
JsInterop assumes that the code it is reasoning about lives in the main window - this isn't the difference of a namespace, but a different global context that it runs under (with different Object, Array types, etc). In Java terms you might consider this not just "wrong package", but "wrong classloader", though in a way that you can't correct very nicely.
Instead, direct the ScriptInjector to put your created JS into the main page, and you can interact with it directly with setWindow:
ScriptInjector.fromUrl(...)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(...);
Alternatively, you can use the "magic" string of "<window>", which will mean "don't provide any namespace at all". This isn't an official part of JsInterop (the constant isn't declared in the jsinterop annotations themselves), but at least presently is a way you can work around this.

Passing Android webview JS interface method as callback function

I have the following android code:
private class MyJavaInterface {
#android.webkit.JavascriptInterface
public void call(String number) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + number));
startActivity(intent);
}
}
...
mWebView.addJavascriptInterface(new MyJavaInterface(), "MyInterface");
This JS code works correctly:
MyInterface.call(number);
And this does not:
var call = MyInterface.call;
call(number);
What am I doing wrong?
This JS code works correctly:
MyInterface.call(number);
Yes, this will work fine because Android will automatically find the class and the only public methods that are annotated.
For your case
MyInterface.call(number) -> new MyJavaInterface().call(number)
But the Second one will not work,
MyInterface.call -> new MyJavaInterface().call - there is no public variable in your class and also there is no support for variable in addJavascriptInterface
Note : Refer
For applications targeted to API level JELLY_BEAN_MR1 and above, only
public methods that are annotated with JavascriptInterface can be
accessed from JavaScript. For applications targeted to API level
JELLY_BEAN or below, all public methods (including the inherited ones)
can be accessed

Don't understand how to pass data from Android Activity to HTML UI when using PhoneGap

I don't understand how it is possible to do this, since there are no WebActivities with PhoneGap, only your Activity classes and your index.html page. I have a MainActivity that looks like this...
public class MastersProjectActivity extends DroidGap
{
#JavascriptInterface
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Set by <content src="index.html" /> in config.xml
//super.loadUrl(Config.getStartUrl());
super.loadUrl("file:///android_asset/www/index.html");
Context myCntxt = getApplicationContext();
TelephonyManager tMgr = (TelephonyManager)myCntxt.getSystemService(Context.TELEPHONY_SERVICE);
String curPhoneNumber = tMgr.getLine1Number();
Log.d("PhoneNumber", curPhoneNumber);
}
}
...and I want to be able to use curPhoneNumber in the index.html page, which in PhoneGap contains the entirety of the UI. Any ideas on how I can do this? I'm completely new to Android development in general, and certainly with PhoneGap. To be honest I still don't really understand how the index.html page is rendered as an Android UI. Do I need to wrap the index.html page in a WebActivity or something? I'm not sure if this would create any issues in the way PhoneGap creates the .apk.
EDIT - I tried to implement but it didn't work for me, all I got was an alert literally saying "interface.getPhoneNumber()". I may have implemented the 'MyInterface' class incorrectly? Here's the class I made....
public class MyInterface extends DroidGap {
private MastersProjectActivity _activity;
private CordovaWebView _view;
public MyInterface(MastersProjectActivity activity, CordovaWebView view) {
this._activity = activity;
this._view = view;
}
#JavascriptInterface
public String getPhoneNumber() {
Context myCtxt = getApplicationContext();
return ((TelephonyManager)myCtxt.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
}
}
...and I exposed the javascript interface in my main activity like so...
#Override
public void onCreate(Bundle savedInstanceState)
{
.
.
.
super.appView.addJavascriptInterface(new MyInterface(this, appView), "interface");
}
...and then I call alert("interface.getPhoneNumber()") in the index.html file, but all I get is that string alerted, it doesn't actually call getPhoneNumber().
You can expose methods in an interface object, called MyInterface, which will be called from the Javascript running inside index.html. For example, you can have a method in MyInterface like:
#JavascriptInterface
public String getPhoneNumber(){
return (TelephonyManager)myCntxt.getSystemService(Context.TELEPHONY_SERVICE).getLine1Number();
}
Then, in your onCreate(), you will expose an instance of MyInterface, like this:
public void onCreate(Bundle savedInstanceState){
//...
super.appView.addJavascriptInterface(new MyInterface(this,appView), "interface");
}
Then, in your index.html, you will have some Javascript that calls this method.
For example,
<script type="text/javascript">
alert("interface.getPhoneNumber()");
</script>
Check out this question for details on how to do it.
EDIT: First, MyInterface does not need to extend DroidGap, e.g., it is a siple POJO.
Second, I made a small mistake in the JS: you don't need the double quotes.
<script type="text/javascript">
alert(window.interface.getPhoneNumber());
</script>
Third, as noted here, you will need to add this line after your super.onCreate():
super.init(); // Calling this is necessary to make this work
One small little think: You can use this._activity as your context, instead of calling getApplicationContext() in getPhoneNumber().

Uncaught TypeError when using a JavascriptInterface

I'm currently displaying a bunch of data to the user as HTML in a webview. I have some links below each entry that should call a method in my app when clicked. The Android WebView's javascript interface seems to be the best (only?) way of handling these things. However, whenever I click the link, I get this error message: ERROR/Web Console(6112): Uncaught TypeError: Object [my namespace]#4075ff10 has no method 'edit' at [base URL]:55
I have the following interface declared:
public class JavaScriptInterface {
Context context;
JavaScriptInterface(Context c) {
context = c;
}
public void edit(String postid) {
Log.d("myApp", "EDIT!");
//do stuff
}
}
I then add it to my WebView:
final WebView threadView = (WebView) findViewById(R.id.webViewThread);
threadView.getSettings().setJavaScriptEnabled(true);
threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
And, finally, I call this within my HTML as follows:
<div class="post-actions">
<div class="right">
<a onClick="Android.edit('4312244');">Edit</a>
</div>
</div>
The real kicker is this all works when I'm debugging my app via the emulator or adb connection to my phone. When I build and publish the app, it breaks.
I'm at my wits end. Any help or advice would be greatly appreciated!
Same problem for my 2.3.3 mobile phone.
But as I knew one app that worked and another not, I was not happy with this workaround.
And I find out the differnce of my two apps.
The one with the broken JavaScriptInterface uses Proguard.
After a little search, I find a solution.
Short summary: interface JavascriptCallback, which is implemented by JavaScriptInterface and added rules for Proguard in proguard.conf:
public interface JavascriptCallback {
}
public class JavaScriptInterface implements JavascriptCallback {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
proguard.cfg:
-keep public class YOURPACKAGENAMEHERE.JavascriptCallback
-keep public class * implements YOURPACKAGENAMEHERE.JavascriptCallback
-keepclassmembers class * implements YOURPACKAGENAMEHERE.JavascriptCallback {
<methods>;
}
So, I'm pleased to say that my problem has been solved. Basically, it's a known bug in Gingerbread, and is present on my 2.3.4 device. After some head scratching, I found this workaround concocted by Jason Shah at PhoneGap. The real kudos for this goes to him as my solution is a slightly modified version of the code in that post.
The WebView
In my onLoad method, I call the following function.
private void configureWebView() {
try {
if (Build.VERSION.RELEASE.startsWith("2.3")) {
javascriptInterfaceBroken = true;
}
} catch (Exception e) {
// Ignore, and assume user javascript interface is working correctly.
}
threadView = (WebView) findViewById(R.id.webViewThread);
threadView.setWebViewClient(new ThreadViewClient());
Log.d(APP_NAME, "Interface Broken? " + javascriptInterfaceBroken.toString());
// Add javascript interface only if it's not broken
iface = new JavaScriptInterface(this);
if (!javascriptInterfaceBroken) {
threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
}
}
There are several things going on here.
In contrast with the PhoneGap method, I'm using a startsWith comparison against the version string. This is because Build.VERSION.RELEASE is 2.3.4 on my reference device. Rather than test against all releases in the 2.3 series, I'm comfortable painting all devices with one brushstroke.
javascriptInterface is a bool initialized to false. JavaScriptInterface, instantiated as iface, is the class that normally handles JS events in my WebView.
ThreadViewClient is the meat and potatoes of my implementation. It's where all the logic for handling the workaround occurs.
The WebViewClient
In the class ThreadViewClient (which extends WebViewClient), I first account for the fact that the js handler that Android normally attaches isn't here. This means that, if I want to use the same javascript calls from within my WebView, I need to duplicate the interface. This is accomplished by inserting custom handlers into the content of your website once it has loaded...
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (javascriptInterfaceBroken) {
final String handleGingerbreadStupidity =
"javascript:function shortSignature(id) { window.location='http://MyHandler:shortSignature:'+id; }; "
+ "javascript: function longSignature(text, username, forumnumber,threadnumber,pagenumber,postid) { var sep='[MyHandler]';"
+ "window.location='http://MyHandler:longSignature:' + encodeURIComponent(text + sep + username + sep + forumnumber + sep + threadnumber + sep + pagenumber + sep + postid);};"
+ "javascript: function handler() { this.shortSignature = shortSignature; this.longSignature = longSignature;}; "
+ "javascript: var Android = new handler();";
view.loadUrl(handleGingerbreadStupidity);
}
}
There's a lot to process there. In the javascript, I define an object handler that contains the functions that map to my js interface. An instance of it is then bound to "Android", which is the same interface name as that used by non-2.3 implementation. This allows for re-use of the code rendered within your webview content.
The functions take advantage of the fact that Android allows one to intercept all navigation that occurs within a WebView. In order to communicate with the outside program, they alter the window location to one with a special signature. I'll get into this in a bit.
Another thing I'm doing is concatenating the parameters of functions with more than one parameter. This allows me to reduce the code complexity within the location handler.
The location handler is also placed in ThreadViewClient...
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Method sMethod = null;
Log.d(APP_NAME, "URL LOADING");
if (javascriptInterfaceBroken) {
if (url.contains("MyHandler")) {
StringTokenizer st = new StringTokenizer(url, ":");
st.nextToken(); // remove the 'http:' portion
st.nextToken(); // remove the '//jshandler' portion
String function = st.nextToken();
String parameter = st.nextToken();
Log.d(APP_NAME, "Handler: " + function + " " + parameter);
try {
if (function.equals("shortSignature")) {
iface.shortSignature(parameter);
} else if (function.equals("longSignature")) {
iface.longSignature(parameter);
} else {
if (sMethod == null) {
sMethod = iface.getClass().getMethod(function, new Class[] { String.class });
}
sMethod.invoke(iface, parameter);
}
}
//Catch & handle SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
return true;
}
}
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}
Here I am intercepting all URL load events that occur in the WebView. If the destination URL contains a magic string, the app attempts to parse it to extract out the method call. Rather than using the tokenizer to extract the individual parameters, I'm passing it to version of my longSignature method that can parse and handle it. This is detailed in the final part of this post.
If, by the time it has exited the "javascriptInterfaceBroken" block, execution has not be returned to the caller, this method treats the URL loading action as a normal link clicked event. In the case of my application I don't want to use the WebView for that, so I pass it off to the operating system via the ACTION_VIEW intent.
This is very similar to the implementation on Jason's blog. However I am bypassing reflection for the most part. I was attempting to use the method in the block with reflection to handle all of my bound functions, but due to my JavaScriptInterface being a nested class I was unable to look into it from another. However, since I defined the interface within the main Activity scope, its methods can be called directly.
Handling Concatenated Parameters
Finally, in my JavaScriptInterface, I created a handler to deal with the case of a concatenated parameter...
public void longSignature(String everything) {
try {
everything = URLDecoder.decode(everything, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(APP_NAME, e);
}
final String[] elements = everything.split("\\[MyHandler\\]");
if (elements.length != 6) {
Toast.makeText(getApplicationContext(), "[" + elements.length + "] wrong number of parameters!", Toast.LENGTH_SHORT).show();
}
else {
longSignature(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]);
}
}
Hooray polymorphism!
And that's my solution! There's a lot of room for improvement, but, for now, this is sufficient. Sorry if some of my conventions have raised your hackles - this is my first Android app and I am unfamiliar with some of the best practices and conventions. Good luck!
You have to annotate (#JavascriptInterface) methods in Java class that you want to make available to JavaScript.
public class JavaScriptInterface {
Context context;
#JavascriptInterface
JavaScriptInterface(Context c) {
context = c;
}
#JavascriptInterface
public void edit(String postid) {
Log.d("myApp", "EDIT!");
//do stuff
} }
Its worked for me. Try out this.
I've taken Jason Shah's and Mr S's implementation as the building block for my fix and improved upon it greatly.
There's just far too much code to put into this comment I'll just link to it.
Details: http://twigstechtips.blogspot.com/2013/09/android-webviewaddjavascriptinterface.html
Source: https://github.com/twig/twigstechtips-snippets/blob/master/GingerbreadJSFixExample.java
Key points are:
Applies to all versions of Gingerbread (2.3.x)
Calls from JS to Android are now synchronous
No longer have to map out interface methods manually
Fixed possibility of string separators breaking code
Much easier to change JS signature and interface names

Categories

Resources