I am using the following code for injecting Java Script in to my Android Web view
WebView
webView = FindViewById<WebView> (Resource.Id.learningWebView);
if (null != webView) {
webView.Settings.JavaScriptEnabled = true;
webView.Settings.SetSupportZoom (true);
webView.SetWebViewClient (new CustomWebViewClient ());
}
WebView Client implementation
public class CustomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
view.LoadUrl (url);
return true;
}
public override void OnPageStarted (WebView view, string url, Android.Graphics.Bitmap favicon)
{
}
public override void OnPageFinished (WebView view, string url)
{
base.OnPageFinished (view, url);
HideLearningDivs (view);
}
void HideLearningDivs (WebView view)
{
try {
view.EvaluateJavascript ("document.getElementById(\"suiteBar\").parentNode.style.display='none'", new JavaScriptResult ());
} catch (Exception ex) {
Console.WriteLine (ex.Message);
}
}
IValueCallback Implementation
public class JavaScriptResult : IValueCallback
{
public IntPtr Handle {
get;
set;
}
public void Dispose ()
{
}
public void OnReceiveValue (Java.Lang.Object result)
{
}
}
But during the time of executing the application I am getting the following error.
java.lang.NoSuchMethodError: no method with name='evaluateJavascript' signature='(Ljava/lang/String;Landroid/webkit/ValueCallback;)V' in class Landroid/webkit/WebView;
Can anyone please help me to find what is wrong with my implementation.
I will link to where I found the answer below, but basically you need to do a check for Android KitKat (4.4), since that function was not introduced until then. If the device is running lower than 4.4, then you may need to do something different to get the value back if you actually need to do something with it. Such as using a Hybrid WebView of some kind (check out Xamarin Forms Labs version of it perhaps) and/or using the AddJavaScriptInterface()
Here is the code:
if(Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Kitkat) {
webView.EvaluateJavascript("javascript:GoBack();", null);
} else {
webView.LoadUrl("javascript:GoBack();");
}
https://forums.xamarin.com/discussion/24894/webview-evaluatejavascript-issues
*Edit: Since writing this, I found an excellent post Adam Pedley (who I apparently have been linking to a lot lately) which covers doing this for Xamarin Forms but also mentions a change in Android 4.2 to the JS engine. Running the JavaScript might work the first time but it also sets the document object to the script result, so you may need to assign the result of document.getElementById() to a variable in order to work around this: var x = document.getElementById().
JavaScriptResult class must be inherited from Java.Lang.Object, like this:
public class JavaScriptResult : Java.Lang.Object, IValueCallback
{
public void OnReceiveValue(Object value)
{
// ...
}
}
Related
UPDATE: it's a confirmed bug. Please upvote it here because it doesn't really receive a lot of attention from MS.
I need to override the shouldInterceptRequest method of WebViewClient to load in-app HTML content following that guide.
Here's the repo with the reproducible code: GitHub. I took a sample code from MS Q&A as well:
// ...
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Microsoft.Maui.Controls.WebView, ProblemHandler2>();
});
// ...
internal class ProblemHandler2 : WebViewHandler
{
protected override Android.Webkit.WebView CreatePlatformView()
{
var wv = new Android.Webkit.WebView(Android.App.Application.Context);
wv.SetWebViewClient(new CustomWebClient());
return wv;
}
}
In the repo, I included 2 custom handlers:
ProblemHandler2 is the exact snippet by the MSFT. I realized a problem: Setting MAUI WebView's Source property no longer navigates the real Android WebView:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, view) =>
{
#if ANDROID
var xWv = handler.PlatformView;
// For ProblemHandler2, this is needed to actually navigate:
xWv.LoadUrl("https://www.google.com/");
#endif
});
this.wv.Source = "https://www.google.com/";
ProblemHandler1 uses the default result and adds a custom handler. This fixes the navigation problem, but, both problem have the same issue:
ShouldInterceptRequest is never called. It is never called on anything even when I manually click a link to navigate. What am I missing? I am sure the CustomWebClient is correctly created and set.
I noticed none of the other callbacks works as well, for example:
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageStarted(view, url, favicon);
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageFinished(view, url);
}
I also tried using WebViewHandler.Mapping but it also does not work:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, _) =>
{
#if ANDROID
handler.PlatformView.SetWebViewClient(new CustomWebClient());
#endif
});
I could be wrong but, I think this might have to do with your overridden version of the CreatePlatform method,
Can you try what the default WebViewHandler is doing:
protected override WebView CreatePlatformView()
{
var platformView = new MauiWebView(this, Context!)
{
LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
};
platformView.Settings.JavaScriptEnabled = true;
platformView.Settings.DomStorageEnabled = true;
platformView.Settings.SetSupportMultipleWindows(true);
return platformView;
}
Check this URL for the default handlers CreatePlatform setup :
https://github.com/dotnet/maui/blob/c6250a20d73e1992b4a02e6f3c26a1e6cbcbe988/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
Also don't use Application Context in Handlers, Handlers have their own Context property you can use.
Yes, it is the case as you said.
And I have created a new issue for this problem, you can follow it up here: https://github.com/dotnet/maui/issues/11004.
Thanks for your support and feedback for maui.
Best Regards.
I'm working with the Android WebView and trying to handle the return of a JavaScript promise from the WebView on the Java side after calling it with evaluateJavascript.
document.java
Button buttonAnnotations = findViewById(R.id.buttonAnnotations);
buttonAnnotations.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
wv.evaluateJavascript("javascript:getAnnotations();", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
Toast.makeText(getApplicationContext(), value, Toast.LENGTH_SHORT).show();
}
});
}
});
index.html
async function getAnnotations() {
await pdfViewer.getAnnotations().then(result => {
return JSON.stringify(result ,null,2);
});
}
If I change the getAnnotations() function to not be async and return a string all works fine, so I'm trying to figure out how to handle this promise in the java code to get the result.
I've seen a few similar questions but none of the answers seemed to work in this case.
As I mentioned in the comment, you are returning a Promise from the async function getAnnotations (which cannot be used directly in onReceiveValue).
In order to "push" the result from getAnnotations back to Android, you have to use the JavascriptInterface:
In your Activity you can define:
#JavascriptInterface
public void onAnnotations(String result) {
Toast.makeText(WebViewActivity.this, result, Toast.LENGTH_LONG).show();
}
and register it with:
webView.addJavascriptInterface(this, "bridge");
In this case the method onAnnotations resists within the Activity which also contains the webView. Therefore I use "this" for the first Argument.
The "bridge" is the namespace, where you can find the function onAnnotations on the JavaScript side.
Now all you have to do, is to make the call from JavaScript to Android:
function getAnnotations() {
pdfViewer.getAnnotations().then(result => {
bridge.onAnnotations(JSON.stringify(result ,null,2));
});
}
Want to add simple paging to ScrollView.
On iOS it was easy adding a customer renderer like this:
[assembly:ExportRenderer (typeof(ScrollView), typeof(JustEat.Ordertracker.Touch.Renderers.ExtendedScrollViewRenderer))]
namespace JustEat.Ordertracker.Touch.Renderers
{
public class ExtendedScrollViewRenderer : ScrollViewRenderer
{
protected override void OnElementChanged (VisualElementChangedEventArgs e)
{
base.OnElementChanged (e);
UIScrollView iosScrollView = (UIScrollView)NativeView;
iosScrollView.PagingEnabled = true;
iosScrollView.ShowsHorizontalScrollIndicator = false;
}
}
}
So doesn't look like android has paging flag so was going to implement with touch events or similar, but can't get any events to trigger on Android side. So have tried hooking up events and overriding:
[assembly:ExportRenderer (typeof(ScrollView), typeof(JustEat.Ordertracker.Touch.Renderers.ExtendedScrollViewRenderer))]
namespace JustEat.Ordertracker.Touch.Renderers
{
public class ExtendedScrollViewRenderer : ScrollViewRenderer
{
protected override void OnElementChanged (VisualElementChangedEventArgs e)
{
base.OnElementChanged (e);
global::Android.Widget.ScrollView droidScrollView = (global::Android.Widget.ScrollView)this;
droidScrollView.HorizontalScrollBarEnabled = false;
droidScrollView.Drag += delegate
{
Console.WriteLine("Drag");
};
}
public override bool OnTouchEvent(global::Android.Views.MotionEvent ev)
{
Console.WriteLine("OnTouchEvent");
return base.OnTouchEvent(ev);
}
}
}
The OnElementChanged definitely gets called as I hit a breakpoint, but the events do nothing, neither does setting HorizontalScrollBarEnabled to false, or even setting .Enabled to false. It's like I have access to a different object?
Found an excellent git repo using same technique to do same thing
https://github.com/chrisriesgo/xamarin-forms-carouselview/issues/24
which works great with 1.x version of forms it uses, but upgrade it to 2.x and it no longer works for above reason.
I had a custom class to override the method shouldOverrideUrlLoading, provided by CordovaWebViewClient.
public class CordovaCustomWebClient extends CordovaWebViewClient {
public CordovaCustomWebClient(CordovaInterface cordova, CordovaWebView view) {
super(cordova, view);
}
#SuppressLint("DefaultLocale")
#SuppressWarnings("deprecation")
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
EventLogger.logMessage(getClass(), "--------------- shouldOverrideUrlLoading ---------------");
return super.shouldOverrideUrlLoading(view, url);
}
It was working fine until I've upgraded to the latest version of Cordova (3.6.3). Now the function shouldOverrideUrlLoading is no longer being called, but when I debug the code I can see that same function being executed in Cordova library (class CordovaWebViewClient).
Here is what I do to override Cordova's web client:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_application);
cordovaWebView = (CordovaWebView) this.findViewById(R.id.mainView);
Config.init(this);
Application application = null;
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
application = (Application) bundle.get("key_application");
}
// Local Url to load application
String url = "";
if (application != null) {
if (HubManagerHelper.getInstance().getApplicationHosted() == null) {
MyApp app = (MyApp) getApplication();
app.registerDefaultHubApplication();
}
url = String.format(WebServicesClient.URL_WEB_APPLICATION, HubManagerHelper.getInstance()
.getApplicationHosted(), application.getPath());
}
cordovaWebView.setWebViewClient(new CordovaCustomWebClient(this, cordovaWebView));
// Listener to Download Web File with Native Component - Download Manager
cordovaWebView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
long contentLength) {
downloadAndOpenFile(WebApplicationActivity.this, url);
}
});
String ua = cordovaWebView.getSettings().getUserAgentString();
String appVersion = getAppVersion();
String newUA = ua.concat(" MyApp." + appVersion);
cordovaWebView.getSettings().setUserAgentString(newUA);
if (savedInstanceState == null) {
cordovaWebView.loadUrl(url);
} else {
((LinearLayout) findViewById(R.id.view_loading)).setVisibility(View.GONE);
}
I hit this exact same problem today after upgrading to 3.6.3. It took looking at the Cordova source code to figure out why this was broken. At some point a new method, init, was introduced, which takes a bunch of params read from your config.xml. If that method is not called by your code, then when a url is loaded it will hit the initIfNecessary case, which in turn will override any custom clients that were set.
From their code:
private void initIfNecessary() {
if (pluginManager == null) {
Log.w(TAG, "CordovaWebView.init() was not called. This will soon be required.");
// Before the refactor to a two-phase init, the Context needed to implement CordovaInterface.
CordovaInterface cdv = (CordovaInterface)getContext();
if (!Config.isInitialized()) {
Config.init(cdv.getActivity());
}
init(cdv, makeWebViewClient(cdv), makeWebChromeClient(cdv), Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
}
}
You can see that makeWebViewClient is called, even though you may have already set your own client.
I solved this with:
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(activity);
CordovaInterface cordova = (CordovaInterface) activity;
init(cordova, new WFWebViewClient(cordova, this), makeWebChromeClient(cordova),
parser.getPluginEntries(), parser.getInternalWhitelist(), parser.getExternalWhitelist(),
parser.getPreferences());
And removed the deprecated use of Config.init(activity);.
Hope this saves you some of the time I've wasted today.
I am having problems with the Webview in Android and it's JavascriptInterfaces.
I am passing a string to the JavascriptInterface. When debugging it, I receive the correct string within my Android application. The problem: Sometimes I get an Uncaught Error: Error calling method on NPObject.
Does anybody know why?
The Interface in Java:
public class JSInterfaceGame extends JSInterface {
#JavascriptInterface
public void setShareText(String share){
shareText = share;
if(mJSInterfaceListener != null)
mJSInterfaceListener.onParametersChanged(SHARE_TEXT);
}
The initialization in the onCreateView-Method within the Fragment:
online = (WebView) rootView.findViewById(R.id.online);
online.setWebViewClient(new WISWebviewClient() {
#Override
public void onStatusChanged(final WebView view, int progress, long duration) {
//unrelated
}
});
WebSettings ws = online.getSettings();
ws.setJavaScriptEnabled(true);
ws.setUserAgentString(USER_AGENT);
ws.setCacheMode(WebSettings.LOAD_DEFAULT);
ws.setRenderPriority(WebSettings.RenderPriority.HIGH);
SharedPreferences settings = getActivity().getSharedPreferences(GameActivity.PREFERENCES, Context.MODE_PRIVATE);
mJSInterface = new JSInterfaceGame();
mJSInterface.setJSInterfaceListener(this); // Defined elsewhere in this class.
mJSInterface.setPlayerName(settings.getString(GameActivity.PREFS_PlAYERNAME, null));
online.addJavascriptInterface(mJSInterface, "JSInterface");
online.loadUrl("http://myurl.something");
Call in Javascript:
function makeShareText() {
var text = "Some text";
console.log(typeof text); // Always a string.
JSInterface.setShareText(text);
}
It happens when you try, using method called from javascript interface, to interact with UI. To solved it in this way:
class mJSInterface()
{
public void myFunction()
{
runOnUiThread(new Runnable() {
public void run() {
//Code that interact with UI
}
});
}
}
To highlight the comment from #Leog
The same error occurs if you call the native javascript function with wrong parameters
This was the source of my error
Another reason can be a RuntimeException on a WebViewCoreThread. Any exception occurred after receiving #JavascriptInterface call will be logged as NPObject error if still running on a WebView thread. Overall insufficient trace message with little clue about the problem.
Correct your issue with handling javascript interface call on a suitable thread.
Example A. (NPObject error):
#JavascriptInterface
public void jsCall() {
Log.v(TAG, "Prepared NullPointerException on "+Thread.currentThread());
String s = null;
s.length(); // This will cause NPObject error
}
Example B. (NullPointerException):
#JavascriptInterface
public void jsCall() {
new Thread(new Runnable() {
#Override
public void run() {
Log.v(TAG, "Prepared NullPointerException on " + Thread.currentThread());
String s = null;
s.length(); // This will throw NullPointerException
}
}).start();
}
Take this as an addition to #Nico.S's answer.
Operate iframe in Android 4.4 WebView may cause a similar exception(Uncaught ReferenceError: NPObject deleted), finally I find out the solution:
#Override
public void onPageFinished(final WebView view, String finishUrl) {
super.onPageFinished(view, finishUrl);
// android 4.4 may lost value of 'Android' when operating iframe
view.addJavascriptInterface(Activity.this, "Android");
}