I am working on a hybrid app and trying to return true or false in onOverrideUrlLoading of webview based on returned value from javascript function executed in webview
Example code I have so far.
//Have a boolean variable isExternalDomain;
//Added JavascriptInterface webView.addJavascriptInterface(this, "android");
public boolean onOverrideUrlLoading(final String url) {
WebView.loadUrl("javascript:android.onData('true')");
//I Tried inserting sleep, delay EG: Thread.sleep(200);
//I see the delay but still javascript executes last.
if(isExternalDomain) {
return true;
} else {
return false;
}
}
#JavascriptInterface public void onData(String value)
{
isExternalDomain = true;
}
So the Issue I am having is javascript execution happens after onOverrideUrlLoading completed executing all lines with isExternalDomain as false. I would like to have onOverrideUrlLoading returning true or false based on javascript returned value.
Unfortunately, running JavaScript code from inside onOverrideUrlLoading() isn't possible. You must return from onOverrideUrlLoading() before WebView can do anything else. When you call WebView.loadUrl() from inside onOverrideUrlLoading(), what really happens is an asynchronous task gets posted onto the WebView's message loop. It only gets processed after you leave onOverrideUrlLoading(). Thus, no amount of delay will make WebView to process your request while your code is inside onOverrideUrlLoading().
If you want to prevent navigation from happening based on the decision made by JavaScript code, it's more natural to do that on the JavaScript side by using window.onbeforeunload event handler. If you return non-null value from it, an attempt to navigate away by clicking a link will be cancelled.
Below is a sample of JavaScript code:
window.onbeforeunload = function() {
if (navigationDisallowed()) {
return true; // Prevent navigating away from the page.
} else {
return null; // Allow navigating away.
}
}
Related
I want to use NativeScript's WebView to preprocess some of my data (it uses XHTML so I must use WebView and it's evaluateJavascript method).
But for the preprocess I don't want to render the whole WebView just for a blink of an eye time, so I created a service which creates a new WebView() instance. Now I can set the src attribute this way and all others, like height and with, but if I want to call the evaluateJavascript function I can't. Code below.
test() {
//create new webview instance
this.wv = new WebView();
//all of this works
this.wv.set('height', 100);
this.wv.setProperty('width', 200);
this.wv.setProperty('src', 'some url here');
//this does not work
this.wv.android.evaluateJavascript(
'function test(){ return 1;} test();',
new android.webkit.ValueCallback({
onReceiveValue: function (res: any) {
console.log(res);
}
})
);
}
The set and setProperty methods are fine, but the evaluateJavascript says it can't be called on undefined.
console.log(this.wv.android); //undefined
Also the WebView loaded event does not run, if have something like this in the test function above.
this.wv.on('loadFinished', (data) => {
console.log('loadFinished');
console.dir(data);
});
On the other hand, if I create a component, put a WebView element in it, than listen for the loadFinished event, than i can run the JS seen above, like this:
webViewLoaded(webargs) {
// console.log('WEBVIEW LOADED');
this.webView = webargs.object;
this.webVie.android.evaluateJavascript(
'function test(){ return 1;} test();',
new android.webkit.ValueCallback({
onReceiveValue: function (res: any) {
console.log(res);
}
})
);
}
//It run properly.
}
So my questions are:
Am I right and the loadFinished event does not run on the creation of new WebView() instance? If it does run, how can I add a listener to it?
What's the difference between the instance of new WebView() and the object returned in the args param above? And why this.wv.android is undefined in the test function above?
Can I run evaluateJavascript on a WebView instance that does not have any HTML counterpart and is not even rendered out? If yes, how?
Am I obligated to render the WebView to use it properly?
i want to delete an element in Xamarin.forms WebView when it's loading but it return null and app crash . how can i do this?
i tried to check if an element exist but dont have any success.
this is my code
protected void OnNavigating(object sender, WebNavigatingEventArgs args)
{
Webview.Eval("const elements = document.getElementsByClassName(\"footer-section\"); while (elements.length > 0) elements[0].remove();");
}
please help me. thanks
You are getting a null exception because the WebView is not loaded when you try to execute Javascript.
In order to prevent this, you can subscribe to OnNavigated Event:
WebView.Navigated Event
Event that is raised after navigation completes.
So, here is a sample:
public YourWebViewPage()
{
InitializeComponent ();
Webview.Navigated += WebViewNavigated;
}
private void WebViewNavigated(object sender, WebNavigatedEventArgs e)
{
Webview.Eval("const elements = document.getElementsByClassName(\"footer-section\"); while (elements.length > 0) elements[0].remove();");
}
You can try to override function onPageCommitVisible
The Android documentation says:
This callback can be used to determine the point at which it is safe
to make a recycled WebView visible, ensuring that no stale content is
shown. It is called at the earliest point at which it can be
guaranteed that WebView#onDraw will no longer draw any content from
previous navigations. The next draw will display either the
WebView#setBackgroundColor of the WebView, or some of the contents of
the newly loaded page.
This method is called when the body of the HTTP response has started
loading, is reflected in the DOM, and will be visible in subsequent
draws. This callback occurs early in the document loading process, and
as such you should expect that linked resources (for example, CSS and
images) may not be available.
You can try the following code:
public override void OnPageCommitVisible(WebView view, string url)
{
string _javascript = "const elements =
document.getElementsByClassName('footer-section'); for(i=0;i<elements.length;i++) {
if(elements[i] != null){ elements[i].parentNode.removeChild(elements[i]); }}";
view.EvaluateJavascript(_javascript, null);
base.OnPageCommitVisible(view, url);
}
Is there a way to launch the Square POS Android app from Xamarin.Forms? I noticed all the SDK examples are in Java. Does the app have to be written using Java?
EDIT: Update
I created Bindings library in Visual Studio using the square android SDK aar file. Now, it launches the Point-of-Sale app but then it afterwords no result data is being sent back to the application. Instead, it goes to an error screen where it says "Webpage not available"
The webpage at
intent#intent;action=com.squareup.pos.action.CHARGE (more..)
could not be loaded because
net: ERROR_UNKNOWN_URL_SCHEME
I am trying to do this in a WebView by overriding ShouldOverrideUrlLoading
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
if (url.StartsWith("http:") || url.StartsWith("https:"))
{
view.LoadUrl(url);
return false;
}
if (url.Contains("CHARGE"))
{
// Process Square Point-Of-Sale Transaction
ChargeRequest chargeRequest =
new ChargeRequest.Builder(1, CurrencyCode.Usd)
.Note("Test")
.RequestMetadata("WEB_CALLBACK_URI=mycallbackurl")
.RestrictTendersTo(ChargeRequest.TenderType.Card, ChargeRequest.TenderType.CardOnFile, ChargeRequest.TenderType.Cash)
.CustomerId("MyCustomerID")
.Build();
try
{
if (PosClient == null)
{
new AlertDialog.Builder(this.activity).SetMessage("Square Point-Of-Sale Client Error").Show();
}
Intent chargeIntent = PosClient.CreateChargeIntent(chargeRequest);
this.activity.StartActivityForResult(chargeIntent, CHARGE_REQUEST_CODE);
}
catch (ActivityNotFoundException e)
{
new AlertDialog.Builder(this.activity).SetMessage(e.Message).Show();
}
}
return false;
}
EDIT: Update
I added an override for OnRecieveError
The errorCode is
Android.Webkit.ClientError.Unsupported Scheme
In the Webview Object there's this
Java.Lang.NoSuchMethodError: no non-static method
"Landroid/webkit/WebView;.getRendererPriorityWaivedWhenNotVisible()Z"
Java.Lang.NoSuchMethodError: no non-static method
"Landroid/webkit/WebView;.getRendererRequestedPriority()I"
EDIT: Update
I have it sort of working now. In the ShouldOverrideUrlLoading method, I changed it to return true if it was processing a transaction. I also had to override OnActivityResult and parse the data coming back from the Android app. It now launches the Square POS App and Returns data back to the calling app. Now, I just have figure out how to process the transaction from there.
What I'm trying to do:
user fills out a form data, clicks submit.
user presented with a loading screen. (HTML element).
Application makes asynchronous call, PhoneGap plugin, which saves this data in db. That's where the problem is, because the call is synchronous instead.
When html app receives a callback, I hide loading screen.
Because of synchronous call that's what I got:
user fills out a form, submits
HTML app freezes, data is being saved to a database.
loading screen appears
callback is called, a few milliseconds after.
Here's some demo (trimmed) code:
Java:
public class SomePlugin extends Plugin
...
public PluginResult execute(String action, JSONArray data, String callbackId)
{
PluginResult result = null;
//
// save data in the background...
//
Log.d("TAG", "Some Message...");
result = new PluginResult(Status.OK, "");
// or
// result = new PluginResult(Status.ERROR);
return result;
}
...
public boolean isSynch(String action) {
return false; // always do async...
}
JavaScript:
$('#loading-screen').show();
var successCallback = function() {
console.log('Success Callback');
$('#loading-screen').hide();
};
var failureCallback = function() {
console.log('Failed Callback');
$('#loading-screen').hide();
};
PhoneGap.exec(successCallback, failureCallback, 'PluginName', 'actionName', data);
From PhoneGap source:
* Execute a PhoneGap command. It is up to the native side whether this action is synch or async.
* The native side can return:
* Synchronous: PluginResult object as a JSON string
* Asynchrounous: Empty string ""
* If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError,
* depending upon the result of the action.
So I thought maybe this line is incorrect in that case:
new PluginResult(Status.OK, "");
Note: If wrap (JavaScript) PhoneGap.exec call in setTimeout (with a delay of 1 sec for example), loading screen will work "properly" (it's still frozen but user have an instant feedback), but that's obviously not a solution.
I think I just not seeing something obvious here, just one parameter or something somewhere.
Thanks.
I think its perfect for an AsyncTask
just process you dbStorage in doInBackground and handle finishing in onPostExcecute().
You are free to update status in onProgressUpdate
I am using a webview to present some formatted stuff in my app. For some interaction (which are specific to certain dom elements) I use javascript and WebView.addJavascriptInterface(). Now, I want to recognize a long touch. Unfortunately, onLongTouch, in Android 2.3 the handles for text selection are displayed.
How can I turn off this text selection without setting the onTouchListener and return true? (Then, the interaction with the "website" doesn't work anymore.
This worked for me
mWebView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
return true;
}
});
mWebView.setLongClickable(false);
I have not tested, if you don't want the vibration caused by the long click, you can try this:
mWebView.setHapticFeedbackEnabled(false);
Setting webkit css property -webkit-user-select to none would solve the problem.
Example CSS to disable selection:
* {
-webkit-user-select: none;
}
I figured it out!! This is how you can implement your own longtouchlistener. In the function longTouch you can make a call to your javascript interface.
var touching = null;
$('selector').each(function() {
this.addEventListener("touchstart", function(e) {
e.preventDefault();
touching = window.setTimeout(longTouch, 500, true);
}, false);
this.addEventListener("touchend", function(e) {
e.preventDefault();
window.clearTimeout(touching);
}, false);
});
function longTouch(e) {
// do something!
}
This works.
It appears that cut/paste via long press is turned off if you used
articleView.setWebChromeClient(new WebChromeClient(){...})
See https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
So if you are using setChromeClient and you WANT to have long click to start copy/paste, the do the following:
webView.setWebChromeClient(new WebChromeClient(){
[.... other overrides....]
// #Override
// https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
// If you DO NOT want to start selection by long click,
// the remove this function
// (All this is undocumented stuff...)
public void onSelectionStart(WebView view) {
// By default we cancel the selection again, thus disabling
// text selection unless the chrome client supports it.
// view.notifySelectDialogDismissed();
}
});
An alternative solution is to subclass WebView and Override performLongClick as bellow:
public class AdvanceWebView extends WebView {
//Add constructors...
#Override
public boolean performLongClick() {
return true;
}
}
It seems that the only option is to set onTouchListener and write your own code to detect long-click. Then return true if it's a long-click and false otherwise.
For kotlin i found the following to work:
webView.isLongClickable = false