90

I have an app that makes extensive use of a WebView. When the user of this app does not have Internet connection, a page saying "web page not available" and various other text appears. Is there a way to not show this generic text in my WebView? I would like to provide my own error handling.

private final Activity activity = this;

private class MyWebViewClient extends WebViewClient
 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
  // I need to do something like this:
  activity.webView.wipeOutThePage();
  activity.myCustomErrorHandling();
  Toast.makeText(activity, description, Toast.LENGTH_LONG).show();
 }
}

I found out WebView->clearView doesn't actually clear the view.

JoJo
  • 19,587
  • 34
  • 106
  • 162
  • 3
    Why don't you check the internet connection before showing the webView and if there is no internet facility available you can skip showing WebView and instead you can show an alert or toast with no internet message? – Andro Selva Jul 12 '11 at 10:03
  • @JoJo can you tick an answer as correct ? probably mine :P – Sherif elKhatib Aug 11 '11 at 00:29

16 Answers16

107

First create your own error page in HTML and put it in your assets folder, Let's call it myerrorpage.html Then with onReceivedError:

mWebView.setWebViewClient(new WebViewClient() {
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        mWebView.loadUrl("file:///android_asset/myerrorpage.html");

    }
});
MysticMagicϡ
  • 28,593
  • 16
  • 73
  • 124
SnowboardBruin
  • 3,645
  • 8
  • 36
  • 59
  • 34
    The old "Webpage not loaded" error page shows for a fraction of a second before being replaced by the custom error page from assets – Cheenu Madan Oct 08 '14 at 10:42
  • 2
    Agree with Cheenu. There has to be a better solution. – Jozua Jul 13 '15 at 14:06
  • 3
    1. You could load the web content while the WebView is invisible (and maybe display a progress bad over it) and show it if the page is loaded successfully/load "myerrorpage.html" on error. 2. You should be aware that onReceiveError will get triggered if there are problems loading the OR problem in JS that runs after the page has been loaded (like jQuery functions that run in the document.ready() event). Desktop browser - will allow JS error without notifying the user about it , as should mobile browsers do. – FunkSoulBrother Aug 26 '15 at 13:17
  • 7
    add `view.loadUrl("about:blank");` before loadUrl to prevent it from showing error page for a fraction of a second. – Aashish Kumar Feb 28 '18 at 13:32
  • 2
    Where to put that myerrorpage.html file? Where is that android assets folder located in? – Nived Kannada May 08 '18 at 07:34
  • Navigate to Packages, right-click on it and select: New -> Folder -> Assets Folder, Click Finish then you will be able to see the assets folder now, create new myerrorpage.html in it. – Mostafa Soliman May 08 '20 at 06:33
39

The best solution I have found is to load an empty page in the OnReceivedError event like this:

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    super.onReceivedError(view, errorCode, description, failingUrl);

    view.loadUrl("about:blank");
}
7heViking
  • 7,137
  • 11
  • 50
  • 94
  • Good idea! In my case I added `view.loadUrl(noConnectionUrl);` twice (where noConnectionUrl is local file with error message). That also helped to not "see" built-in error page for a fraction of a second. – kasparspr Nov 27 '17 at 08:46
  • 5
    Please note that, if you are handling `webView.goBack()` then make sure to handle condition for this blank page. Otherwise your previous page will be loaded and if failed again then you will see this blank page again. – Chintan Shah Mar 06 '19 at 05:29
  • So, What is the best way to handle it? – Mostafa Soliman May 08 '20 at 06:05
  • @MoSoli It all depends on your needs. An alternative to displaying an empty page would be to show some custom ui or a cached webpage. – 7heViking May 11 '20 at 12:55
11

Finally, I solved this. (It works till now..)

My solution is like this...

  1. Prepare the layout to show when an error occurred instead of Web Page (a dirty 'page not found message') The layout has one button, "RELOAD" with some guide messages.

  2. If an error occurred, Remember using boolean and show the layout we prepare.

  3. If user click "RELOAD" button, set mbErrorOccured to false. And Set mbReloadPressed to true.
  4. if mbErrorOccured is false and mbReloadPressed is true, it means webview loaded page successfully. 'Cause if error occurred again, mbErrorOccured will be set true on onReceivedError(...)

Here is my full source. Check this out.

public class MyWebViewActivity extends ActionBarActivity implements OnClickListener {

    private final String TAG = MyWebViewActivity.class.getSimpleName();
    private WebView mWebView = null;
    private final String URL = "http://www.google.com";
    private LinearLayout mlLayoutRequestError = null;
    private Handler mhErrorLayoutHide = null;

    private boolean mbErrorOccured = false;
    private boolean mbReloadPressed = false;

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);

        ((Button) findViewById(R.id.btnRetry)).setOnClickListener(this);
        mlLayoutRequestError = (LinearLayout) findViewById(R.id.lLayoutRequestError);
        mhErrorLayoutHide = getErrorLayoutHideHandler();

        mWebView = (WebView) findViewById(R.id.webviewMain);
        mWebView.setWebViewClient(new MyWebViewClient());
        WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        mWebView.setWebChromeClient(getChromeClient());
        mWebView.loadUrl(URL);
    }

    @Override
    public boolean onSupportNavigateUp() {
        return super.onSupportNavigateUp();
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();

        if (id == R.id.btnRetry) {
            if (!mbErrorOccured) {
                return;
            }

            mbReloadPressed = true;
            mWebView.reload();
            mbErrorOccured = false;
        }
    }

    @Override
    public void onBackPressed() {
        if (mWebView.canGoBack()) {
            mWebView.goBack();
            return;
        }
        else {
            finish();
        }

        super.onBackPressed();
    }

    class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return super.shouldOverrideUrlLoading(view, url);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }

        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (mbErrorOccured == false && mbReloadPressed) {
                hideErrorLayout();
                mbReloadPressed = false;
            }

            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            mbErrorOccured = true;
            showErrorLayout();
            super.onReceivedError(view, errorCode, description, failingUrl);
        }
    }

    private WebChromeClient getChromeClient() {
        final ProgressDialog progressDialog = new ProgressDialog(MyWebViewActivity.this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setCancelable(false);

        return new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
            }
        };
    }

    private void showErrorLayout() {
        mlLayoutRequestError.setVisibility(View.VISIBLE);
    }

    private void hideErrorLayout() {
        mhErrorLayoutHide.sendEmptyMessageDelayed(10000, 200);
    }

    private Handler getErrorLayoutHideHandler() {
        return new Handler() {
            @Override
            public void handleMessage(Message msg) {
                mlLayoutRequestError.setVisibility(View.GONE);
                super.handleMessage(msg);
            }
        };
    }
}

Addition:Here is layout....

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rLayoutWithWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<WebView
    android:id="@+id/webviewMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<LinearLayout
    android:id="@+id/lLayoutRequestError"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:background="@color/white"
    android:gravity="center"
    android:orientation="vertical"
    android:visibility="gone" >

    <Button
        android:id="@+id/btnRetry"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:minWidth="120dp"
        android:text="RELOAD"
        android:textSize="20dp"
        android:textStyle="bold" />
</LinearLayout>

cmcromance
  • 2,289
  • 1
  • 25
  • 26
9

When webview is embedded in some custom view such that user almost believes that he is seeing a native view and not a webview, in such scenario showing the "page could not be loaded" error is preposterous. What I usually do in such situation is I load blank page and show a toast message as below

webView.setWebViewClient(new WebViewClient() {

            @Override
            public void onReceivedError(WebView view, int errorCode,
                    String description, String failingUrl) {
                Log.e(TAG," Error occured while loading the web page at Url"+ failingUrl+"." +description);     
                view.loadUrl("about:blank");
                Toast.makeText(App.getContext(), "Error occured, please check newtwork connectivity", Toast.LENGTH_SHORT).show();
                super.onReceivedError(view, errorCode, description, failingUrl);
            }
        });
Jeevan
  • 8,532
  • 14
  • 49
  • 67
7

Check out the discussion at Android WebView onReceivedError(). It's quite long, but the consensus seems to be that a) you can't stop the "web page not available" page appearing, but b) you could always load an empty page after you get an onReceivedError

Community
  • 1
  • 1
Torid
  • 4,176
  • 1
  • 28
  • 29
  • These guys are talking about `onReceivedError` not triggering at all. It's triggering correctly in my code when there's no Internet connection. – JoJo Jul 01 '11 at 21:22
2

Here I found the easiest solution. check out this..

   @Override
        public void onReceivedError(WebView view, int errorCode,
                                    String description, String failingUrl) {
//                view.loadUrl("about:blank");
            mWebView.stopLoading();
            if (!mUtils.isInterentConnection()) {
                Toast.makeText(ReportingActivity.this, "Please Check Internet Connection!", Toast.LENGTH_SHORT).show();
            }
            super.onReceivedError(view, errorCode, description, failingUrl);
        }

And here is isInterentConnection() method...

public boolean isInterentConnection() {
    ConnectivityManager manager = (ConnectivityManager) mContext
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    if (manager != null) {
        NetworkInfo info[] = manager.getAllNetworkInfo();
        if (info != null) {
            for (int i = 0; i < info.length; i++) {
                if (info[i].getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
    }
    return false;
}
Ankush Joshi
  • 416
  • 3
  • 9
2

You could use a GET request to get the page content and then display that data using the Webview , this way you are not using multiple server calls. Alternative you can use Javascript to check the DOM object for validity.

Ravi Vyas
  • 12,212
  • 6
  • 31
  • 47
2

Perhaps I misunderstand the question, but it sounds like you're saying you get the error received callback, and you just are asking what is the best way to not show the error? Why don't you just either remove the web view from the screen and/or show another view on top of it?

Matthew Horst
  • 1,991
  • 15
  • 18
1

I suppose that if you insist on doing this, you could just check if the resource is there before calling the loadURL function. Just simply override the functions and do the check before calling the super()

REMARK (maybe off-topic): In http, there is a method called HEAD which is described as follow:

The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response

This method might be handy. Anyway how ever you implement it ... check this code:

import java.util.Map;

import android.content.Context;
import android.webkit.WebView;

public class WebViewPreLoad extends WebView{

public WebViewPreLoad(Context context) {
super(context);
}
public void loadUrl(String str){
if(//Check if exists)
super.loadUrl(str);
else
//handle error
}
public void loadUrl(String url, Map<String,String> extraHeaders){
if(//Check if exists)
super.loadUrl(url, extraHeaders);
else
//handle error
}
}

You could try this check using

if(url.openConnection().getContentLength() > 0)
Sherif elKhatib
  • 45,786
  • 16
  • 89
  • 106
  • 7
    Checking if the resource is there before actually going there would double the load on the server because there would be 2 physical requests per 1 virtual request. This seems a little overkill. – JoJo Jul 08 '11 at 23:38
  • 1
    Anyway you could still overload it and get the html content ,, check whether it has an error. If not parse and display it – Sherif elKhatib Jul 10 '11 at 01:41
  • how would one actually implement this? – JoJo Jul 11 '11 at 02:26
  • @JoJo, actually this method reduces load on the server in the event of an invalid url as returning a HEAD response has a far smaller overhead vs returning a 304 or 500 response. – Justin Shield Jul 12 '11 at 02:25
  • This doesn't address the problem if the connection drops while downloading the page – Pedro Loureiro Oct 02 '12 at 16:22
0

I've been working on this problem of ditching those irritable Google error pages today. It is possible with the Android example code seen above and in plenty of other forums (ask how I know):

wv.setWebViewClient(new WebViewClient() {
    public void onReceivedError(WebView view, int errorCode,
                            String description, String failingUrl) {
       if (view.canGoBack()) {
          view.goBack();
        }
       Toast.makeText(getBaseContext(), description, Toast.LENGTH_LONG).show();
       }
    }
});

IF you put it in shouldOverrideUrlLoading() as one more webclient. At least, this is working for me on my 2.3.6 device. We'll see where else it works later. That would only depress me now, I'm sure. The goBack bit is mine. You may not want it.

R Earle Harris
  • 985
  • 9
  • 17
0

Try this

 @SuppressWarnings("deprecation")
 @Override
 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
      // Your handling
 }

 @Override
 public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError rerr) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          onReceivedError(view, rerr.getErrorCode(), rerr.getDescription().toString(), req.getUrl().toString());
      }
 }
Virat18
  • 3,427
  • 2
  • 23
  • 29
0

We can set the visibility of webView to 0(view.INVISIBLE) and show some message. This code works for my app running on lolipop.

@SuppressWarnings("deprecation")
    @Override
    public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
        // hide webView content and show custom msg
        webView.setVisibility(View.INVISIBLE);
        Toast.makeText(NrumWebViewActivity.this,getString(R.string.server_not_responding), Toast.LENGTH_SHORT).show();
    }

    @TargetApi(android.os.Build.VERSION_CODES.M)
    @Override
    public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError rerr) {
        // Redirect to deprecated method, so you can use it in all SDK versions
        onReceivedError(view, rerr.getErrorCode(), rerr.getDescription().toString(), req.getUrl().toString());
    }
Mahen
  • 760
  • 9
  • 12
0

I've had to face this issue and also tried to solve it from different perspectives. Finally I found a solution by using a single flag to check if an error happened.

... extends WebViewClient {

    boolean error;

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {

        showLoading(true);
        super.onPageStarted(view, url, favicon);

        error  = false; // IMPORTANT
    }

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        if(!error) {
            Observable.timer(100, TimeUnit.MICROSECONDS, AndroidSchedulers.mainThread())
                    .subscribe((data) -> view.setVisibility(View.VISIBLE) );
        }

        showLoading(false);
    }


    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

        view.stopLoading();

        view.setVisibility(View.INVISIBLE) 
        error  = true;  

        // Handle the error
    }


     @Override
    @TargetApi(android.os.Build.VERSION_CODES.M)
    public void onReceivedError(WebView view,
                                WebResourceRequest request,
                                WebResourceError error) {

        this.onReceivedError(view, error.getErrorCode(),
                error.getDescription().toString(),
                request.getUrl().toString());
    }
 }

This way I hide the page every time there's an error and show it when the page has loaded again properly.

Also added a small delay in case.

I avoided the solution of loading an empty page as it does not allow you to do webview.reload() later on due to it adds that new page in the navigation history.

Jose M Lechon
  • 5,766
  • 6
  • 44
  • 60
0

I would just change the webpage to whatever you are using for error handling:

getWindow().requestFeature(Window.FEATURE_PROGRESS);  
webview.getSettings().setJavaScriptEnabled(true);  
final Activity activity = this;  
webview.setWebChromeClient(new WebChromeClient() {  
public void onProgressChanged(WebView view, int progress) {  
 // Activities and WebViews measure progress with different scales.  
 // The progress meter will automatically disappear when we reach 100%  
 activity.setProgress(progress * 1000);  
 }  
});  
webview.setWebViewClient(new WebViewClient() {  
public void onReceivedError(WebView view, int errorCode, String description, String 
failingUrl) {  
 Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();  
}  
});  
webview.loadUrl("http://slashdot.org/");

this can all be found on http://developer.android.com/reference/android/webkit/WebView.html

Ephraim
  • 8,352
  • 9
  • 31
  • 48
-1

override your WebViewClient method

@Override
public void onReceivedError(WebView view, int errorCode,
    String description, String failingUrl) {
    view.clearView();
}

view.loadUrl('about:blank') has side-effects, as it cancels the original URL loading.

Mike G
  • 4,232
  • 9
  • 40
  • 66
duckduckgo
  • 1,280
  • 1
  • 18
  • 32
  • view.clearView() is deprecated, see http://stackoverflow.com/questions/3715482/clear-webview-content – silva96 Dec 09 '15 at 19:55
-1

try this shouldOverrideUrlLoading , before redirect to another url check for internet conncetion based on that page should be loaded or not.

Community
  • 1
  • 1
Bipin Vayalu
  • 3,025
  • 2
  • 25
  • 39