Is there a way I can put an if statement inside shouldOverrideUrlLoading() which checks for web/mobile access. Then display an error message instead of the nasty page not found page that mobile chrome displays.
Something similar to
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
if(isOnline() == false)
{
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage("Mobile device currnetly has no internet access. Please try again.");
dlgAlert.setTitle("No Connection");
dlgAlert.setPositiveButton("OK", null);
dlgAlert.setCancelable(true);
dlgAlert.setIcon(R.drawable.ic_launcher);
dlgAlert.create().show();
}
return false;
}
Ok I found the solution for my problem. First of all I put the shouldOverrideUrlLoading() in new WevViewclient. Before I had it outside of that block.
Next I was getting an error message for
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
Next I changed the code to this, using Toast messages instead of AlertDialog Builder.
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
if (isOnline() == false)
{
Toast msg = Toast.makeText(MainActivity.this,"No mobile or web access, try again later.",Toast.LENGTH_LONG);
msg.show();
return true;
}
else
{
return false;
}
}
Related
I'm using a webview in xamarin, i followed many tutorials to handle navigation, and all works fine.
My issue is : when an anchor tag has a target="_blank" the event Navigating is never fired.
I see arround someone give a javascript solution which remove target=_blank and attach it at the end of href link.
Is really that the right way to do that? Look wired..
Thank you
This is initialization in xamarin.android renderer
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.JavaScriptCanOpenWindowsAutomatically = true;
Control.Settings.SetSupportMultipleWindows(true);
Control.Settings.AllowFileAccessFromFileURLs = true;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.UserAgentString = Control.Settings.UserAgentString + " crmvw";
Android.Webkit.WebChromeClient xCC = new CustChromeWebViewClient(_context);
Control.SetWebChromeClient(xCC);
Control.SetWebViewClient(new CrmWebViewClient(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(((HybridWebView)Element).Uri);
}
}
And this is my navigating event, never fired when anchor has target=_blank
private void webv_Navigating(object sender, WebNavigatingEventArgs e)
{
if (IsFirstLoad) {
IsBusy = true;
IsFirstLoad = false;
}
if (e.Url.ToLower().StartsWith("tel:") || e.Url.ToString().StartsWith("wtai:") || e.Url.ToLower().StartsWith("sms:") || e.Url.ToLower().StartsWith("mailto:"))
{
e.Cancel = true;
}
}
here my override function for URL in my custom WEBView
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, Android.Webkit.IWebResourceRequest request)
{
Android.Net.Uri url = request.Url;
if (url.ToString().StartsWith("tel:") || url.ToString().StartsWith("wtai:"))
{
Xamarin.Essentials.PhoneDialer.Open(UtilityXam.Contact.GetPhoneFromHTML(url.ToString()));
return true;
}else if (url.ToString().StartsWith("mailto:"))
{
UtilityXam.Contact xE = new UtilityXam.Contact();
string xEmail = UtilityXam.Contact.GetEmailFromHTML( url.ToString());
var xTask = xE.SendEmail("","",new System.Collections.Generic.List<string>(){ xEmail });
return true;
}
else if (url.ToString().StartsWith("sms:"))
{
UtilityXam.Contact xE = new UtilityXam.Contact();
string xPh = UtilityXam.Contact.GetPhoneFromHTML(url.ToString());
var xTask = xE.SendSMS("", "", new System.Collections.Generic.List<string>() { xPh });
}
else
{
view.LoadUrl(url.ToString());
}
view.SetDownloadListener(new CrmDownloadListener(_context));
return true;
}
After the great help of Jack Hua i'm able to solve the problem.
In OnElementChanged of Hybrid renderer i set support for multiple windows.
Control.Settings.SetSupportMultipleWindows(true);
and next i had to menage onCreateWindow event in the custom chrome webview.
Here the code converted in c# from the link suggested by Jack.
public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Android.OS.Message resultMsg)
{
Android.Webkit.WebView newWebView = new Android.Webkit.WebView(_context);
view.AddView(newWebView);
Android.Webkit.WebView.WebViewTransport transport = (Android.Webkit.WebView.WebViewTransport) resultMsg.Obj;
transport.WebView = newWebView;
resultMsg.SendToTarget();
return true;
}
This is an introduced bug in Xamarin Forms since v4.8.0.1364 (According to the bug report at least)
You can work around it for now by removing the target="_blank" from the url or by setting a property
webView.Settings.SetSupportMultipleWindows(true);
I have fixed it for our app by striping target="_blank" and target='_blank' in some replacement logic that already runs over the content
There are multiple open issues reporting it for Xamarin Forms github
[Bug] Cannot open URLs with WebView android when the target is _blank #12917
[Bug] Android WebView's Navigating and Navigated events not fired #12852
I tried a completely different approach, because the above answers didn't really help (my target _blank links would always open in a new chrome instance and not in the in-app browser).
First, you'll need to set SetSupportMultipleWindows to false. As soon as you do that, all the windows will open in the same webView:
Control.Settings.SetSupportMultipleWindows(false);
More information on how you set these settings: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview
Next, all I did was change the back-button behaviour, to make sure the back button doesn't close the app and instead navigates the webview pages (HybridWebView is my custom webview that I created in the first step).
HybridWebView _browser;
public MainPage()
{
_browser = new HybridWebView
{
Source = "https://brandschutzplaner-stahltragwerke.promat.ch"
};
Content = _browser;
}
protected override bool OnBackButtonPressed()
{
base.OnBackButtonPressed();
if (_browser.CanGoBack)
{
_browser.GoBack();
return true;
}
else
{
base.OnBackButtonPressed();
return true;
}
}
I have created an Android app using WebView. This app shows the content of a website. There are many internal links and external links (with a 301 redirect). Internal links are correct, but when I tap on external links, I see a fullscreen desktop layout instead of the responsive version of the page and it looks very bad.
How do I edit the following code in order to always get the responsive layout to fit the screen?
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(final WebView view, String url) {
// hide loading image
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
progressBar.setVisibility(View.GONE);
}
}, 1000);
}
#Override
public void onReceivedError(WebView view, int errorCode,String description, String failingUrl) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(description).setPositiveButton(getText(R.string.ok), null).setTitle("onReceivedError");
builder.show();
}
});
// load url (if connection available
if (isInternetConnected(this)) {
String URL = "http://www.example.com/";
webView.loadUrl(URL);
}else{
// showAlertDialog(this, "No Internet Connection",
// "You don't have internet connection.", false);
new AlertDialog.Builder(this)
.setTitle("No Internet Connection")
.setMessage("You don't have internet connection.")
.setCancelable(false)
.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// whatever...
finish(); //Close current activity
startActivity(getIntent()); //Restart it
}
}).create().show();
}
Actually you are testing on a tablet and amazon website looks similar to desktop version from a tablet, while it becomes responsive when browsed from a phone.
Please try from a standard phone and you'll see that its mobile version of the site :)
Please let me know in case your problem isn't solved.
Thanks
Sanskar
In my app i have a function that checks the entered text from a displayed AlertDialog with an input text. If the text is equal to a string variable, return True, else return False, and catch this resulting value to continue conditional code.
But it seems its a little difficult to do this as i've read in other posts asking how to solve the same problem.
I've already done this:
private boolean checkAdministratorPassword() {
final enterPasswordResult[0] = false;
AlertDialog.Builder alert = new AlertDialog.Builder(mContext);
alert.setTitle("Confirm action");
alert.setIcon(R.drawable.ic_launcher);
alert.setMessage("Enter administrator pass to continue");
final EditText input = new EditText(mContext);
input.setPadding(5, 0, 5, 0);
alert.setView(input);
alert.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String strPass = input.getEditableText().toString();
if (strPass.length() == 0) {
dialog.cancel();
}
if (strPass.equalsIgnoreCase(Constantes.ADMIN_PASS)) {
enterPasswordResult[0] = true;
dialog.cancel();
} else {
Toast.makeText(mContext, "Invalid pass..!!", Toast.LENGTH_LONG).show();
dialog.cancel();
}
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alertDialog = alert.create();
alertDialog.show();
return enterPasswordResult[0];
}
And i call the function this way:
If ( checkAdministratorPassword() == True ){
//true conditions
}
But the problem is that the check function doesnt wait for the result to continue with the code, it just continue by itself and i dont get the appropiate behavior.
The issue is you're trying to handle an async event in the logcal flow of your program. You can do this if you make the Dialog it's own class and use an Interface to callback to your host activity. Check out the documentation on DialogFragment.
public interface PasswordCheckListener{
public void valid(boolean check);
}
private static class PasswordDialog extends DialogFragment {
private PasswordCheckListener listener;
public static PaswordDialog newInstance(PasswordCheckListener listener){
this.listener = listener;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
//Put your dialog creation code here
}
private checkAdminPassword(){
//Whatever your check passowrd code is
listener.valid(result);
}
}
I realize I didn't implement all the code for you but that's the general idea. By using an interface you can call back to your host Activity or Fragment when the user enters the password and presses submit. You can then handle the event as it happens, rather than having to deal with it in your program flow.
Thank you all for your answers!! i've found the right way to achieve this problem by creating an Activity whith theme "Theme.Dialog", an input text and two buttons (Accept, Cancel), i start this activity for result asking the user to enter the administrator pass to continue, checking the string and then returning again to onActivityResult() from previous activity with the correct information to proceed.
I try to catch webview longclicks to show a context menu. (see code below)
When longclicking an image, I always get the image-URL as extra (for a not linked image with IMAGE_TYPE and for a linked image with SRC_IMAGE_ANCHOR_TYPE).
But how can I get the Link-URL (and not the image-URL) for an image with a hyperlink?
Best,
Sebastian
mywebview.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
final WebView webview = (WebView) v;
final WebView.HitTestResult result = webview.getHitTestResult();
if (result.getType() == SRC_ANCHOR_TYPE) {
return true;
}
if (result.getType() == SRC_IMAGE_ANCHOR_TYPE) {
return true;
}
if (result.getType() == IMAGE_TYPE) {
return true;
}
return false;
}
});
None of solutions above worked for me on Android 4.2.2. So I looked into source code of default android web browser. I extracted solution to this exact problem - get link-URL from image link.
Source:
https://github.com/android/platform_packages_apps_browser/blob/master/src/com/android/browser/Controller.java
Extracted solution:
LongClick listener:
...
mWebview.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
HitTestResult result = mWebview.getHitTestResult();
if (result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
Message msg = mHandler.obtainMessage();
mWebview.requestFocusNodeHref(msg);
}
}
});
...
Handler to get the URL:
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
// Get link-URL.
String url = (String) msg.getData().get("url");
// Do something with it.
if (url != null) ...
}
};
I know this is an old issue, but I recently came across this issue. Based on Perry_ml answer, I used the following Kotlin code to resolve it:
webView.setOnLongClickListener {
val result = webView.hitTestResult
if (result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
val handler = Handler()
val message = handler.obtainMessage()
webView.requestFocusNodeHref(message)
val url = message.data.getString("url")
// Do something with url, return true as touch has been handled
true
} else {
false
}
}
I posted some information about it here.
I checked the source code of the WebView and it seems that the image uri is the only extra data you can get for SRC_IMAGE_ANCHOR_TYPE. But don't be mad here I have a quick and dirty workaround for you:
webview.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final WebView webview = (WebView) v;
final HitTestResult result = webview.getHitTestResult();
if(result.getType()==HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
webview.setWebViewClient(new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 2. and here we get the url (remember to remove the WebView client and return true so that the hyperlink will not be really triggered)
mUrl = url; // mUrl is a member variant of the activity
view.setWebViewClient(null);
return true;
}
});
// 1. the picture must be focused, so we simulate a DPAD enter event to trigger the hyperlink
KeyEvent event1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
webview.dispatchKeyEvent(event1);
KeyEvent event2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
webview.dispatchKeyEvent(event2);
// 3. now you can do something with the anchor url (and then clear the mUrl for future usage)
String url = mUrl;
if (url!=null) {
Toast.makeText(webview.getContext(), url, Toast.LENGTH_SHORT).show();
}
mUrl = null;
}
return false;
}
});
I tried the code on a low-end Android 2.1 device and a high-end Android 4.0 device, both work like a charm.
Regards
Ziteng Chen
Ziteng Chen solution works up to Android 4.0 (API Level 15) but for some reason the KeyEvent down & up doesn't work in API LEVEL 16+ (Android 4.1+ JELLY_BEAN). It doesn't fire the WebView's loadUrl. So I had to replace the dispatchKeyEvent with dispatchTouchEvent. Here's the code:
...
MotionEvent touchDown = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, touchX, touchY, 0);
webView.dispatchTouchEvent(touchDown);
touchDown.recycle();
MotionEvent touchUp = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, touchX, touchY, 0);
webView.dispatchTouchEvent(touchUp);
touchUp.recycle();
String url = mUrl;
...
You'd probably have to wait (use an AsyncTask) to get the mUrl in slower devices where it's null immediately after firing the dispatchTouchEvents
Hope it helps.
Instead of calling this function myWebView.requestFocusNodeHref(msg);, try calling this function myWebView.requestImageRef(msg);
All I want to do is make sure the user knows that a webservice is processing so they don't repeatedly mash buttons while it tries to work.
EditText partnumber = (EditText)FindViewById(Resource.Id.itemNumber);
partnumber.FocusChange += (object sender, View.FocusChangeEventArgs e) =>
{
if (!e.HasFocus)
{
var pd = ProgressDialog.Show(this, "Processing", "Please Wait...", false);
var res = new InventoryApp();
res.partValidateCompleted += delegate { pd.Hide(); };
var isValid = res.partValidate(partnumber.Text);
if (isValid == "Not Found")
{
partnumber.Text = "";
partnumber.RequestFocus();
partqty.ClearFocus();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle("Invalid Part");
builder.SetMessage("Part number does not exist in database. Please ensure you are entering the correct part number and try again");
builder.SetPositiveButton("OK", delegate { });
var dialog = builder.Create();
dialog.Show();
}
else
{
partdesc.Text = isValid;
}
}
};
If I leave pd.Hide() in the code then I never see the progressdialog. If I take it out, I only see the progressdialog after the webservice returns its results and then stays up because I haven't hidden it anywhere. How do I get it to show while the app waits for partValidate and when can I hide it?
Judging from your code, it looks like the partValidate() method is synchronous. Since this code is being executed on the UI thread, the app's UI will be blocked for the duration of that method call, which would explain why you don't see any UI updates until it finishes. You could get around this by running the call on a background thread, and then moving back to the UI thread once you need to update the UI:
partnumber.FocusChange += (object sender, View.FocusChangeEventArgs e) =>
{
if (!e.HasFocus)
{
var pd = ProgressDialog.Show(this, "Processing", "Please Wait...", false);
var res = new InventoryApp();
res.partValidateCompleted += delegate { pd.Hide(); };
ThreadPool.QueueUserWorkItem(state =>
{
var isValid = res.partValidate(partnumber.Text);
RunOnUiThread(() =>
{
if (isValid == "Not Found")
{
partnumber.Text = "";
partnumber.RequestFocus();
partqty.ClearFocus();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle("Invalid Part");
builder.SetMessage("Part number does not exist in database. Please ensure you are entering the correct part number and try again");
builder.SetPositiveButton("OK", delegate { });
var dialog = builder.Create();
dialog.Show();
}
else
{
partdesc.Text = isValid;
}
}
}
}
};
I have a blog post up here that explains various methods of dealing with this scenario on Mono for Android applications.
As i understood from your question code in partValidate is async but there is no parameter for OnComplete callback. You can solve your problem adding it and your code will be something like this:
var isValid = res.partValidate(partnumber.Text,() => {pd.Hide();});
But the better way is to create implementation of AsyncTask class for you web service call.
Here you can find demo