I'm working with PhoneGap and Android and have my .html and js files on an external server. When I use the following code, the app loads my external .html files and everything works fine:
this.setIntegerProperty("loadUrlTimeoutValue", 60000);
this.loadUrl("http://www.myserver.com");
However, when work via a WebView I can't seem to set the loadURLTimeoutValue for a WebView:
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
webView = (WebView) findViewById(R.id.webview);
webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
webView.loadUrl("http://www.myserver.com");
}
This doesn't work. How can I set the timeout value on the WebView?
This is a workaround to simulate the described behavior. You can use a WebViewClient, and override the onPageStarted method:
public class MyWebViewClient extends WebViewClient {
boolean timeout;
public MyWebViewClient() {
timeout = true;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
new Thread(new Runnable() {
#Override
public void run() {
timeout = true;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(timeout) {
// do what you want
}
}
}).start();
}
#Override
public void onPageFinished(WebView view, String url) {
timeout = false;
}
}
If timeout, you can load, for example, an error page...
To add the WebViewClient to you WebView, just do this:
webView.setWebViewClient(new MyWebViewClient());
I used this to set a time out for my WebView:
public class MyWebViewClient extends WebViewClient {
boolean timeout = true;
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Runnable run = new Runnable() {
public void run() {
if(timeout) {
// do what you want
showAlert("Connection Timed out", "Whoops! Something went wrong. Please try again later.");
}
}
};
Handler myHandler = new Handler(Looper.myLooper());
myHandler.postDelayed(run, 5000);
}
#Override
public void onPageFinished(WebView view, String url) {
timeout = false;
}
}
The correct way to change the default timeout is using tag <preference /> in config.xml file, for example:
<preference name="loglevel" value="DEBUG" />
<preference name="loadUrlTimeoutValue" value="60000" />
<preference name="errorUrl" value="file:///android_asset/www/connection_error.html" />
For more preference options, refer to Android Configuration.
If you extend CordovaWebView, which you should in order to get the phonegap API, you can just use the following:
this.getIntent().putExtra("loadUrlTimeoutValue", 60000);
Internally, CordovaWebView implements a timeout mechanism similar to the ones proposed in the previous post (default timeout = 2000).
Mind that this is not a documented interface, so it might break in the future.
WebView mWebView = findViewById(R.id.web_view);
mWebView.setWebViewClient(new WebViewClient() {
private volatile boolean timeout;
private volatile String timeoutOnPageStartedURL;
private volatile String timeoutCurrentURL;
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
timeout = true;
timeoutOnPageStartedURL = url;
timeoutCurrentURL = view.getUrl();
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (timeout) {
view.post(new Runnable() {
#Override
public void run() {
String currentURL = view.getUrl();
if ((timeoutOnPageStartedURL.hashCode() == currentURL.hashCode()) ||
(timeoutCurrentURL.hashCode() == currentURL.hashCode())) {
// do what you want with UI
}
}
});
}
}
}).start();
}
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
timeout = false;
}
});
The use of Thread class bothers me calling the WebView from the run function will lead to an exception as the WebView is created and used in another thread.
I would perform this with an AsyncTask.
In this example, I use an AsyncTask to load an error file into the WebView if the timeout was reached. If the page loads properly I cancel the AsyncTask.
The onPostExecute runs in the UI thread so there is no thread safety problem to interact with the WebView:
private class CustomWebViewClient extends WebViewClient {
boolean mPageLoaded;
final int mTimeoutLength = 20000;
WebView mView;
TimeoutCheck timeoutCheckTask;
public CustomWebViewClient()
{
super();
mPageLoaded = false;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mView = view;
timeoutCheckTask = new TimeoutCheck();
timeoutCheckTask.execute(null,null);
}
public void onPageFinished(WebView view, String url) {
mPageLoaded = true;
timeoutCheckTask.cancel(true);
}
private class TimeoutCheck extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
long count = 0;
while (count < mTimeoutLength)
{
try
{
Thread.sleep( 1000 );
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
// Escape early if cancel() is called
if (isCancelled()) break;
count += 1000;
}
return null;
}
protected void onPostExecute(Void result) {
if(!mPageLoaded) {
mbLoadedErrFile = true;
//load error file into the webview
mView.loadUrl("file:///android_asset/url_err_timeout.html");
}
}
}
Related
Need some advice. I want to be able to redirect the user to a specific link in WebView. Example: we go to the first link and if it directs us to "google.com" then we will have to direct the user to "http://test.com". I made an implementation, put a redirect in shouldOverrideUrlLoading, however, after SplashScreen-loading image done its work, a white background appears and that's it. At the same time, if you remove the code for redirection, then after performing its work, the SplashScreen - loading image, the first link opens and the page from the Internet is displayed correctly. How do I redirect to other pages after opening the first one?
public class MainActivity extends AppCompatActivity {
private WebView webView;
private final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("http://test/start");
new TaskForRedirecting().execute();
}
public class TaskForRedirecting extends AsyncTask<Boolean, Void, Boolean> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Boolean doInBackground(Boolean... booleans) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Start loading here");
WebViewClient webViewClient = new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("google.com")) {
view.loadUrl("http://test1.com");
return false;
} else if (url.contains("yahoo.com")) {
view.loadUrl("http://test2.com");
return false;
} else {
}
return true;
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (request.getUrl().toString() == "google.com") {
view.loadUrl("http://test1.com");
view.loadUrl(request.getUrl().toString());
return false;
} else if (request.getUrl().toString() == "yahoo.com") {
view.loadUrl("http://test2.com");
return false;
}
return true;
}
};
webView.setWebViewClient(webViewClient);
Log.d(TAG, "Do in background: APP is Working!!!");
}
});
return false;
}
#Override
protected void onPostExecute(Boolean success) {
success = false;
if (success) {
Toast toast = Toast.makeText(MainActivity.this, "App is under maintenance!", Toast.LENGTH_SHORT);
toast.show();
} else {
Toast toast = Toast.makeText(MainActivity.this, "Let's start to work!", Toast.LENGTH_SHORT);
toast.show();
}
Log.d(TAG, "upload - final! ");
}
}
}
public class SplashScreenActivity extends AppCompatActivity {
long Delay = 6000;
private final String TAG = "SplashScreenActivity";
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_splash_screen);
new SplashScreen().execute();
}
public class SplashScreen extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void...voids) {
Timer RunSplash = new Timer();
TimerTask ShowSplash = new TimerTask() {
#Override
public void run() {
finish();
Intent intent = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(intent);
}
};
RunSplash.schedule(ShowSplash, Delay);
Log.d(TAG, "ShowSplash - final! ");
return null;
}
}
}
Your implementation needs to change a bit, we don't need to set the webview client inside an AsyncTask, instead, we can set it directly but it should be set before we call loadurl.
I have removed the asynctask, and set the client in onCreate method before calling loadUrl.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
WebViewClient webViewClient = new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("google.com")) {
view.loadUrl("http://test1.com");
return true;
} else if (url.contains("yahoo.com")) {
view.loadUrl("http://test2.com");
return true;
}
return false;
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (request.getUrl().toString() == "google.com") {
view.loadUrl("http://test1.com");
return true;
} else if (request.getUrl().toString() == "yahoo.com") {
view.loadUrl("http://test2.com");
return true;
}
return false;
}
};
webView.setWebViewClient(webViewClient);
webView.loadUrl("http://test/start");
}
UPDATE
Please check out the shouldOverrideUrlLoading Doc, You should return true when you want to cancel the current load, so when you want to load a different URL, then return true else return false to continue the current load. Returning false means the URL which is being loaded to the webview will continue, your custom URL will not be loaded.
I have a webview on my 4.1.2 Android, which I load with
public class FullscreenActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
final WebView wv=(WebView)findViewById(R.id.webView1);
String sHTML="Webviewtest";
for(int i=0;i<500;i++){
sHTML=sHTML+"Line " + i + "<br>";
}
wv.loadData(sHTML, "text/html", null);
wv.setScrollY(1500);
wv.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
wv.setScrollY(500);
Toast.makeText(getApplicationContext(), "Done! " + wv.getScrollY(), Toast.LENGTH_SHORT).show();
}
});
}
}
I like the webview to scroll down to a specific postition, but it stays on the top.
any ideas ?
When setScrollY() with a button it works, but I like to set the position after laoding the HTML. I also tried to do it with a Handler, which I call in onPageFinished. This does not work too.
I created a very simple Android project, where everyone can download and test:
https://drive.google.com/folderview?id=0B7yz_IQ1CMblUU03cGd5cEotTVE&usp=sharing
This time it is not android's fault,it has provided us with another approach.
try:
public void onPageFinished(WebView v, String url) {
v.evaluateJavascript("window.scrollTo(0,"+expectedPos/a.getResources().getDisplayMetrics().density+");", null);
super.onPageFinished(v, url);
}
My Solution. I don't like it, but it works. All this mess because of the android framework, that doesn't work like expected...
I added a AsyncTask:
private class Waitforloaded extends AsyncTask<String, Integer, Long> {
protected void onProgressUpdate(Integer... progress) {
}
protected void onPostExecute(Long result) {
}
#Override
protected Long doInBackground(String... params) {
// TODO Auto-generated method stub
boolean bgo=true;
int iTries=0;
while(bgo){
iTries=iTries+1;
if(iTries>20 || wv.getContentHeight()>0){
bgo=false;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
wv.setScrollY(1500);
return null;
}
}
Then calling it in
wv.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Waitforloaded wfl=new Waitforloaded();
wfl.execute("");
}
WebView.setScrollY works for me only when I give it some delay with a timer after the WebView is loaded:
webView.loadData(sHtmlTemplate, "text/html", "utf-8");
Timer t = new Timer(false);
t.schedule(new TimerTask() {
#Override
public void run() {
MainActivity.mainactivity.runOnUiThread(new Runnable() {
public void run() {
webView.setScrollY(500);
}
});
}
}, 300); // 300 ms delay before scrolling
EDIT:
I am using a ProgressBar for a Webview that works fine. Now I want to refresh my Webview, which also works. When I am trying to combine them, the Spinner doesn't end at all. This is my code:
private final Runnable m_Runnable = new Runnable()
{
public void run()
{
//Toast.makeText(WebcamsScreen.this,"in runnable",Toast.LENGTH_SHORT).show();
doRefreshingStuff(id);
WebcamsScreen.this.mHandler.postDelayed(m_Runnable, 20000);
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialitions
doRefreshingStuff(id);
this.mHandler = new Handler();
this.mHandler.postDelayed(m_Runnable,20000);
doRefreshingStuff(id);
}
public void doRefreshingStuff(String id){
setContentView(R.layout.web4);
webView7 = (WebView) findViewById(R.id.web_4_1);
webView7.getSettings().setJavaScriptEnabled(true);
webView7.getSettings().setJavaScriptEnabled(true);
progressBar = ProgressDialog.show(WebcamsScreen.this, "example", "...");
url="my url";
webView7.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url1) {
view.loadUrl(url1);
return true;
}
public void onPageFinished(WebView view, String url1) {
if (progressBar.isShowing()) {
progressBar.dismiss();
}
}
});
webView7.loadUrl(url);
what must I change?
It looks like you are refreshing your WebView every 20 seconds and popping up a new progress dialog when you do. Your code assumes that the old progress dialog has already been dismissed. If it hasn't, then the progress dialog just became orphaned and will remain on display forever. You can probably fix this by inserting this code:
if (progressBar != null && progressBar.isShowing()) {
progressBar.dismiss();
}
just before the line progressBar = ....
But I wouldn't fix your code that way. You are doing a huge amount of work that you don't need to do. (For instance, you don't need to reload the same content view.) Here's my revised version:
private final Runnable m_Runnable = new Runnable() {
public void run() {
doRefreshingStuff(id);
mHandler.postDelayed(m_Runnable, 20000);
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialitions
setContentView(R.layout.web4);
webView7 = (WebView) findViewById(R.id.web_4_1);
webView7.getSettings().setJavaScriptEnabled(true);
webView7.getSettings().setJavaScriptEnabled(true);
webView7.setWebViewClient(new WebViewClient() {
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (!mProgressBar.isShowing()) {
mProgressBar.show();
}
}
public void onPageFinished(WebView view, String url1) {
if (mProgressBar.isShowing()) {
mProgressBar.dismiss();
}
}
});
mProgressBar = new ProgressDialog(this);
mProgressBar.setTitle("example");
mProgressBar.setMessage("...");
mHandler = new Handler();
}
protected void onResume() {
super.onResume();
mHandler.post(m_Runnable);
}
protected void onPause() {
super.onPause();
mHandler.removeCallbacks(m_Runnable);
}
public void doRefreshingStuff(String id) {
url="my url";
webView7.loadUrl(url);
}
Note that I moved the starting of the page loading loop to onResume() and kill the looping in onPause(). You may want slightly different logic, but you should definitely stop looping somehow when your activity pauses or finishes.
I want to get a return value from Javascript in Android. I can do it with the iPhone, but I can't with Android. I used loadUrl, but it returned void instead of an object. Can anybody help me?
Same as Keith but shorter answer
webView.addJavascriptInterface(this, "android");
webView.loadUrl("javascript:android.onData(functionThatReturnsSomething)");
And implement the function
#JavascriptInterface
public void onData(String value) {
//.. do something with the data
}
Don't forget to remove the onData from proguard list (if you have enabled proguard)
Here's a hack on how you can accomplish it:
Add this Client to your WebView:
final class MyWebChromeClient extends WebChromeClient {
#Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d("LogTag", message);
result.confirm();
return true;
}
}
Now in your javascript call do:
webView.loadUrl("javascript:alert(functionThatReturnsSomething)");
Now in the onJsAlert call "message" will contain the returned value.
Use addJavascriptInterface() to add a Java object to the Javascript environment. Have your Javascript call a method on that Java object to supply its "return value".
Here's what I came up with today. It's thread-safe, reasonably efficient, and allows for synchronous Javascript execution from Java for an Android WebView.
Works in Android 2.2 and up. (Requires commons-lang because I need my code snippets passed to eval() as a Javascript string. You could remove this dependency by wrapping the code not in quotation marks, but in function(){})
First, add this to your Javascript file:
function evalJsForAndroid(evalJs_index, jsString) {
var evalJs_result = "";
try {
evalJs_result = ""+eval(jsString);
} catch (e) {
console.log(e);
}
androidInterface.processReturnValue(evalJs_index, evalJs_result);
}
Then, add this to your Android activity:
private Handler handler = new Handler();
private final AtomicInteger evalJsIndex = new AtomicInteger(0);
private final Map<Integer, String> jsReturnValues = new HashMap<Integer, String>();
private final Object jsReturnValueLock = new Object();
private WebView webView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
webView = (WebView) findViewById(R.id.webView);
webView.addJavascriptInterface(new MyJavascriptInterface(this), "androidInterface");
}
public String evalJs(final String js) {
final int index = evalJsIndex.incrementAndGet();
handler.post(new Runnable() {
public void run() {
webView.loadUrl("javascript:evalJsForAndroid(" + index + ", " +
"\"" + StringEscapeUtils.escapeEcmaScript(js) + "\")");
}
});
return waitForJsReturnValue(index, 10000);
}
private String waitForJsReturnValue(int index, int waitMs) {
long start = System.currentTimeMillis();
while (true) {
long elapsed = System.currentTimeMillis() - start;
if (elapsed > waitMs)
break;
synchronized (jsReturnValueLock) {
String value = jsReturnValues.remove(index);
if (value != null)
return value;
long toWait = waitMs - (System.currentTimeMillis() - start);
if (toWait > 0)
try {
jsReturnValueLock.wait(toWait);
} catch (InterruptedException e) {
break;
}
else
break;
}
}
Log.e("MyActivity", "Giving up; waited " + (waitMs/1000) + "sec for return value " + index);
return "";
}
private void processJsReturnValue(int index, String value) {
synchronized (jsReturnValueLock) {
jsReturnValues.put(index, value);
jsReturnValueLock.notifyAll();
}
}
private static class MyJavascriptInterface {
private MyActivity activity;
public MyJavascriptInterface(MyActivity activity) {
this.activity = activity;
}
// this annotation is required in Jelly Bean and later:
#JavascriptInterface
public void processReturnValue(int index, String value) {
activity.processJsReturnValue(index, value);
}
}
On API 19+, the best way to do this is to call evaluateJavascript on your WebView:
webView.evaluateJavascript("foo.bar()", new ValueCallback<String>() {
#Override public void onReceiveValue(String value) {
// value is the result returned by the Javascript as JSON
}
});
Related answer with more detail: https://stackoverflow.com/a/20377857
The solution that #Felix Khazin suggested works, but there is one key point missing.
The javascript call should be made after the web page in the WebView is loaded. Add this WebViewClient to the WebView, along with the WebChromeClient.
Full Example:
#Override
public void onCreate(Bundle savedInstanceState) {
...
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyWebViewClient());
webView.setWebChromeClient(new MyWebChromeClient());
webView.loadUrl("http://example.com");
}
private class MyWebViewClient extends WebViewClient {
#Override
public void onPageFinished (WebView view, String url){
view.loadUrl("javascript:alert(functionThatReturnsSomething())");
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
}
private class MyWebChromeClient extends WebChromeClient {
#Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d("LogTag", message);
result.confirm();
return true;
}
}
As an alternative variant that uses a custom scheme to communicate Android native code <-> HTML/JS code. for example MRAID uses this technic[About]
MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
final WebView webview = new CustomWebView(this);
setContentView(webview);
webview.loadUrl("file:///android_asset/customPage.html");
webview.postDelayed(new Runnable() {
#Override
public void run() {
//Android -> JS
webview.loadUrl("javascript:showToast()");
}
}, 1000);
}
}
CustomWebView
public class CustomWebView extends WebView {
public CustomWebView(Context context) {
super(context);
setup();
}
#SuppressLint("SetJavaScriptEnabled")
private void setup() {
setWebViewClient(new AdWebViewClient());
getSettings().setJavaScriptEnabled(true);
}
private class AdWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("customschema://")) {
//parse uri
Toast.makeText(CustomWebView.this.getContext(), "event was received", Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
}
}
customPage.html (located in the assets folded)
<!DOCTYPE html>
<html>
<head>
<title>JavaScript View</title>
<script type="text/javascript">
<!--JS -> Android-->
function showToast() {
window.location = "customschema://goto/";
}
</script>
</head>
<body>
</body>
</html>
You can do it like this:
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
public WebView web_view;
public static TextView textView;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Window.AddFlags(WindowManagerFlags.Fullscreen);
Window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.main);
web_view = FindViewById<WebView>(Resource.Id.webView);
textView = FindViewById<TextView>(Resource.Id.textView);
web_view.Settings.JavaScriptEnabled = true;
web_view.SetWebViewClient(new SMOSWebViewClient());
web_view.LoadUrl("https://stns.egyptair.com");
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class SMOSWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
view.LoadUrl(request.Url.ToString());
return false;
}
public override void OnPageFinished(WebView view, string url)
{
view.EvaluateJavascript("document.getElementsByClassName('notf')[0].innerHTML;", new JavascriptResult());
}
}
public class JavascriptResult : Java.Lang.Object, IValueCallback
{
public string Result;
public void OnReceiveValue(Java.Lang.Object result)
{
string json = ((Java.Lang.String)result).ToString();
Result = json;
MainActivity.textView.Text = Result.Replace("\"", string.Empty);
}
}
I have a WebView that is loading a page from the Internet. I want to show a ProgressBar until the loading is complete.
How do I listen for the completion of page loading of a WebView?
Extend WebViewClient and call onPageFinished() as follows:
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
// do your stuff here
}
});
#ian this is not 100% accurate. If you have several iframes in a page you will have multiple onPageFinished (and onPageStarted). And if you have several redirects it may also fail. This approach solves (almost) all the problems:
boolean loadingFinished = true;
boolean redirect = false;
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String urlNewString) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(urlNewString);
return true;
}
#Override
public void onPageStarted(WebView view, String url) {
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
}
#Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
//HIDE LOADING IT HAS FINISHED
} else {
redirect = false;
}
}
});
UPDATE:
According to the documentation:
onPageStarted will NOT be called when the contents of an embedded frame changes, i.e. clicking a link whose target is an iframe.
I found a specific case like that on Twitter where only a pageFinished was called and messed the logic a bit. To solve that I added a scheduled task to remove loading after X seconds. This is not needed in all the other cases.
UPDATE 2:
Now with current Android WebView implementation:
boolean loadingFinished = true;
boolean redirect = false;
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(
WebView view, WebResourceRequest request) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(request.getUrl().toString());
return true;
}
#Override
public void onPageStarted(
WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
}
#Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
//HIDE LOADING IT HAS FINISHED
} else {
redirect = false;
}
}
});
I am pretty partial to #NeTeInStEiN (and #polen) solution but would have implemented it with a counter instead of multiple booleans or state watchers (just another flavor but I thought might share). It does have a JS nuance about it but I feel the logic is a little easier to understand.
private void setupWebViewClient() {
webView.setWebViewClient(new WebViewClient() {
private int running = 0; // Could be public if you want a timer to check.
#Override
public boolean shouldOverrideUrlLoading(WebView webView, String urlNewString) {
running++;
webView.loadUrl(urlNewString);
return true;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
running = Math.max(running, 1); // First request move it to 1.
}
#Override
public void onPageFinished(WebView view, String url) {
if(--running == 0) { // just "running--;" if you add a timer.
// TODO: finished... if you want to fire a method.
}
}
});
}
I found one elegant solution as well, have not tested it rigorously though:
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (webView.getProgress() == 100) {
progressBar.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
}
}
If you want show a progress bar you need to listen for a progress change event, not just for the completion of page:
mWebView.setWebChromeClient(new WebChromeClient(){
#Override
public void onProgressChanged(WebView view, int newProgress) {
//change your progress bar
}
});
BTW if you want display just an Indeterminate ProgressBar overriding the method onPageFinished is enough
I have simplified NeTeInStEiN's code to be like this:
mWebView.setWebViewClient(new WebViewClient() {
private int webViewPreviousState;
private final int PAGE_STARTED = 0x1;
private final int PAGE_REDIRECTED = 0x2;
#Override
public boolean shouldOverrideUrlLoading(WebView view, String urlNewString) {
webViewPreviousState = PAGE_REDIRECTED;
mWebView.loadUrl(urlNewString);
return true;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
webViewPreviousState = PAGE_STARTED;
if (dialog == null || !dialog.isShowing())
dialog = ProgressDialog.show(WebViewActivity.this, "", getString(R.string.loadingMessege), true, true,
new OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
// do something
}
});
}
#Override
public void onPageFinished(WebView view, String url) {
if (webViewPreviousState == PAGE_STARTED) {
dialog.dismiss();
dialog = null;
}
}
});
It is easy to understand, OnPageFinished if the previous callback is on onPageStarted, so the page is completely loaded.
for Kotlin users:
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
// do your logic
}
}
there are a lot of methods that you can override though
Use setWebViewClient() and override onPageFinished()
You can trace the Progress Staus by the getProgress method in webview class.
Initialize the progress status
private int mProgressStatus = 0;
then the AsyncTask for loading like this:
private class Task_News_ArticleView extends AsyncTask<Void, Void, Void> {
private final ProgressDialog dialog = new ProgressDialog(
your_class.this);
// can use UI thread here
protected void onPreExecute() {
this.dialog.setMessage("Loading...");
this.dialog.setCancelable(false);
this.dialog.show();
}
#Override
protected Void doInBackground(Void... params) {
try {
while (mProgressStatus < 100) {
mProgressStatus = webview.getProgress();
}
} catch (Exception e) {
}
return null;
}
protected void onPostExecute(Void result) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
}
}
thanks for the answers. It helped me, but I had to improve it a bit for my needs. I had several pagestarts and finishes so I added a timer which checks if atfer the pagefinish is started a new pagestart. Okay, bad explanation. See the code :)
myWebView.setWebViewClient(new WebViewClient() {
boolean loadingFinished = true;
boolean redirect = false;
long last_page_start;
long now;
// Load the url
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
view.loadUrl(url);
return false;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i("p","pagestart");
loadingFinished = false;
last_page_start = System.nanoTime();
show_splash();
}
// When finish loading page
public void onPageFinished(WebView view, String url) {
Log.i("p","pagefinish");
if(!redirect){
loadingFinished = true;
}
//call remove_splash in 500 miSec
if(loadingFinished && !redirect){
now = System.nanoTime();
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
remove_splash();
}
},
500);
} else{
redirect = false;
}
}
private void show_splash() {
if(myWebView.getVisibility() == View.VISIBLE) {
myWebView.setVisibility(View.GONE);
myWebView_splash.setVisibility(View.VISIBLE);
}
}
//if a new "page start" was fired dont remove splash screen
private void remove_splash() {
if (last_page_start < now) {
myWebView.setVisibility(View.VISIBLE);
myWebView_splash.setVisibility(View.GONE);
}
}
});
Here's a novel method for detected when a URL has loaded by utilising Android's capability for JavaScript hooks. Using this pattern, we exploit JavaScript's knowledge of the document's state to generate a native method call within the Android runtime. These JavaScript-accessible calls can be made using the #JavaScriptInterface annotation.
This implementation requires that we call setJavaScriptEnabled(true) on the WebView's settings, so it might not be suitable depending on your application's requirements, e.g. security concerns.
src/io/github/cawfree/webviewcallback/MainActivity.java (Jelly Bean, API Level 16)
package io.github.cawfree.webviewcallback;
/**
* Created by Alex Thomas (#Cawfree), 30/03/2017.
**/
import android.net.http.SslError;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
/** An Activity demonstrating how to introduce a callback mechanism into Android's WebView. */
public class MainActivity extends AppCompatActivity {
/* Static Declarations. */
private static final String HOOK_JS = "Android";
private static final String URL_TEST = "http://www.zonal.co.uk/";
private static final String URL_PREPARE_WEBVIEW = "";
/* Member Variables. */
private WebView mWebView = null;
/** Create the Activity. */
#Override protected final void onCreate(final Bundle pSavedInstanceState) {
// Initialize the parent definition.
super.onCreate(pSavedInstanceState);
// Set the Content View.
this.setContentView(R.layout.activity_main);
// Fetch the WebView.
this.mWebView = (WebView)this.findViewById(R.id.webView);
// Enable JavaScript.
this.getWebView().getSettings().setJavaScriptEnabled(true);
// Define the custom WebClient. (Here I'm just suppressing security errors, since older Android devices struggle with TLS.)
this.getWebView().setWebViewClient(new WebViewClient() { #Override public final void onReceivedSslError(final WebView pWebView, final SslErrorHandler pSslErrorHandler, final SslError pSslError) { pSslErrorHandler.proceed(); } });
// Define the WebView JavaScript hook.
this.getWebView().addJavascriptInterface(this, MainActivity.HOOK_JS);
// Make this initial call to prepare JavaScript execution.
this.getWebView().loadUrl(MainActivity.URL_PREPARE_WEBVIEW);
}
/** When the Activity is Resumed. */
#Override protected final void onPostResume() {
// Handle as usual.
super.onPostResume();
// Load the URL as usual.
this.getWebView().loadUrl(MainActivity.URL_TEST);
// Use JavaScript to embed a hook to Android's MainActivity. (The onExportPageLoaded() function implements the callback, whilst we add some tests for the state of the WebPage so as to infer when to export the event.)
this.getWebView().loadUrl("javascript:" + "function onExportPageLoaded() { " + MainActivity.HOOK_JS + ".onPageLoaded(); }" + "if(document.readyState === 'complete') { onExportPageLoaded(); } else { window.addEventListener('onload', function () { onExportPageLoaded(); }, false); }");
}
/** Javascript-accessible callback for declaring when a page has loaded. */
#JavascriptInterface #SuppressWarnings("unused") public final void onPageLoaded() {
// Display the Message.
Toast.makeText(this, "Page has loaded!", Toast.LENGTH_SHORT).show();
}
/* Getters. */
public final WebView getWebView() {
return this.mWebView;
}
}
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Essentially, we're appending an additional JavaScript function that is used to test the state of the document. If it's loaded, we launch a custom onPageLoaded() event in Android's MainActivity; otherwise, we register an event listener that updates Android once the page is ready, using window.addEventListener('onload', ...);.
Since we're appending this script after the call to this.getWebView().loadURL("") has been made, it's probable that we don't need to 'listen' for the events at all, since we only get a chance to append the JavaScript hook by making a successive call to loadURL, once the page has already been loaded.
Just to show progress bar, "onPageStarted" and "onPageFinished" methods are enough; but if you want to have an "is_loading" flag (along with page redirects, ...), this methods may executed with non-sequencing, like "onPageStarted > onPageStarted > onPageFinished > onPageFinished" queue.
But with my short test (test it yourself.), "onProgressChanged" method values queue is "0-100 > 0-100 > 0-100 > ..."
private boolean is_loading = false;
webView.setWebChromeClient(new MyWebChromeClient(context));
private final class MyWebChromeClient extends WebChromeClient{
#Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 0){
is_loading = true;
} else if (newProgress == 100){
is_loading = false;
}
super.onProgressChanged(view, newProgress);
}
}
Also set "is_loading = false" on activity close, if it is a static variable because activity can be finished before page finish.
The renderer will not finish rendering when the OnPageFinshed method is called or the progress reaches 100% so both methods don't guarantee you that the view was completely rendered.
But you can figure out from OnLoadResource method what has been already rendered and what is still rendering. And this method gets called several times.
#Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
// Log and see all the urls and know exactly what is being rendered and visible. If you wanna know when the entire page is completely rendered, find the last url from log and check it with if clause and implement your logic there.
if (url.contains("assets/loginpage/img/ui/forms/")) {
// loginpage is rendered and visible now.
// your logic here.
}
}
this will been called before he start loading the page
(and get the same parameters as onFinished())
#Override
public void onPageCommitVisible(WebView view, String url) {
super.onPageCommitVisible(view, url);
}
Loading url with SwipeRefreshLayout and ProgressBar:
UrlPageActivity.java:
WebView webView;
SwipeRefreshLayout _swipe_procesbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_url_page);
String url = "http://stackoverflow.com/";
_swipe_procesbar = (SwipeRefreshLayout)findViewById(R.id.url_path_swipe_procesbar);
_swipe_procesbar.post(new Runnable() {
#Override
public void run() {
_swipe_procesbar.setRefreshing(true);
}
}
);
webView = (WebView) findViewById(R.id.url_page_web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
_swipe_procesbar.setRefreshing(false);
_swipe_procesbar.setEnabled(false);
}
});
webView.loadUrl(url);
}
activity_url_page.xml:
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/url_path_swipe_procesbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.test.test1.UrlPageActivity">
<WebView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/url_page_web_view" />
</RelativeLayout>
</android.support.v4.widget.SwipeRefreshLayout>
Here's a method which allows you to register a Runnable to be executed once a particular web address has finished loading. We associate each Runnable with a corresponding URL String in a Map, and we use the WebView's getOriginalUrl() method to choose the appropriate callback.
package io.github.cawfree.webviewcallback;
/**
* Created by Alex Thomas (#Cawfree), 30/03/2017.
**/
import android.net.http.SslError;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.util.HashMap;
import java.util.Map;
/** An Activity demonstrating how to introduce a callback mechanism into Android's WebView. */
public class MainActivity extends AppCompatActivity {
/* Member Variables. */
private WebView mWebView;
private Map<String, Runnable> mCallbackMap;
/** Create the Activity. */
#Override protected final void onCreate(final Bundle pSavedInstanceState) {
// Initialize the parent definition.
super.onCreate(pSavedInstanceState);
// Set the Content View.
this.setContentView(R.layout.activity_main);
// Fetch the WebView.
this.mWebView = (WebView)this.findViewById(R.id.webView);
this.mCallbackMap = new HashMap<>();
// Define the custom WebClient. (Here I'm just suppressing security errors, since older Android devices struggle with TLS.)
this.getWebView().setWebViewClient(new WebViewClient() {
/** Handle when a request has been launched. */
#Override public final void onPageFinished(final WebView pWebView, final String pUrl) {
// Determine whether we're allowed to process the Runnable; if the page hadn't been redirected, or if we've finished redirection.
if(pUrl.equals(pWebView.getOriginalUrl())) {
// Fetch the Runnable for the OriginalUrl.
final Runnable lRunnable = getCallbackMap().get(pWebView.getOriginalUrl());
// Is it valid?
if(lRunnable != null) { lRunnable.run(); }
}
// Handle as usual.
super.onPageFinished(pWebView, pUrl);
}
/** Ensure we handle SSL state properly. */
#Override public final void onReceivedSslError(final WebView pWebView, final SslErrorHandler pSslErrorHandler, final SslError pSslError) { pSslErrorHandler.proceed(); }
});
// Assert that we wish to visit Zonal's website.
this.getWebView().loadUrl("http://www.zonal.co.uk/");
// Align a Callback for Zonal; this will be serviced once the page has loaded.
this.getCallbackMap().put("http://www.zonal.co.uk/", new Runnable() { #Override public void run() { /* Do something. */ } });
}
/* Getters. */
public final WebView getWebView() {
return this.mWebView;
}
private final Map<String, Runnable> getCallbackMap() {
return this.mCallbackMap;
}
}
Use this it should help.`var currentUrl = "google.com"
var partOfUrl = currentUrl.substring(0, currentUrl.length-2)
webView.setWebViewClient(object: WebViewClient() {
override fun onLoadResource(WebView view, String url) {
//call loadUrl() method here
// also check if url contains partOfUrl, if not load it differently.
if(url.contains(partOfUrl, true)) {
//it should work if you reach inside this if scope.
} else if(!(currentUrl.startWith("w", true))) {
webView.loadurl("www.$currentUrl")
} else if(!(currentUrl.startWith("h", true))) {
webView.loadurl("https://$currentUrl")
} else { ...}
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
// you can call again loadUrl from here too if there is any error.
}
//You should also override other override method for error such as onReceiveError to see how all these methods are called one after another and how they behave while debugging with break point.
}
`
Kotlin solution
First Solution is create webviewclient as private class and it is more efficient.
In the other hand second soltion is sorter :)
First Solution
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView : WebView = findViewById(R.id.webView)
webView.webViewClient = MyWebViewClient()
}
private class MyWebViewClient : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
println("Load Started")
}
override fun onPageFinished(view: WebView, url: String) {
println("Load Finished")
}
}
}
Second Solution
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView : WebView = findViewById(R.id.webView)
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
println("Load Started")
}
override fun onPageFinished(view: WebView, url: String) {
println("Load Finished")
}
}
}
Both solution actually same. So onPageStarted function run when page started to load in the other hang onPageFinished function works when page loaded entirely. You may want to write your as an example loadFinishedRun() function inside onPageFinished funtion.
Better late than never