How to detect when Android WebView has loaded page? - android

I have an app that displays a splash page and removed that splash page when a URL is loaded in WebView. The following is the relevant code we are using to remove the splash page:
browser.setWebViewClient(new BrowserClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Work around for WebView onPageFinished called twice
if (flag == true) {
browser.setVisibility(View.VISIBLE);
splashImage.setVisibility(View.INVISIBLE);
pageLoader.setVisibility(View.INVISIBLE);
} else {
flag = true;
}
}
});
This code works... except it is slow. The splash page takes far too long to remove, long after the webpage has loaded.
Are there any tips on how I can reliably detect when WebView has loaded a page? I've been researching this for the past few days and I can't seem to come up with anything that is reliable.
The most promising I saw is the following, but putting this code throws an error in Android Console:
#Override
public void invalidate() {
super.invalidate();
if (getContentHeight() > 0) {
// WebView has displayed some content and is scrollable.
}
}
Thanks!
EDIT: There are a lot of answers proposing onPageFinished, and even someone marking this as a duplicate with a link to solutions using onPageFinished. Folks, we already are using onPageFinished. We are looking for an alternative to onPageFinished due to how unreliable it is.

If you need than you can achieve this by loaded page progress on using this web client, you can use this also.
webView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
Log.e("progress", ""+progress);
if (progress == 100) { //...page is fully loaded.
// TODO - Add whatever code you need here based on web page load completion...
}
}
});

Xamarin/MAUI solution (see OnPageFinished):
webView.SetWebViewClient(new CustomWebViewClient());
...
public class CustomWebViewClient : WebViewClient
{
public CustomWebViewClient()
{
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
// Your Logic comes here
}
}

You should use WebChromeClient.onProgressChanged() to obtain the current progress of loading a page.

If the splash screen you are using is a simple static image then perhaps you can try the following approach:
In XML nest the splash screen (ImageView) inside the WebView:
<WebView android:id="#+id/wv_your_web_view_id"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:id="#+id/iv_your_splash_screen_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#mipmap/img_the_coolest_image_ever"
android:contentDescription="#string/splash_screen_desc"/>
</WebView>
Now for the runtime code:
WebView wvUltimo = findViewById(R.id.wv_your_web_view_id);
wvUltimo.loadUrl(REQUESTED_WEB_PAGE_URL); // e.g. "https://www.google.com"
wvUltimo.getSettings().setJavaScriptEnabled(true); // Only enable this if you need Javascript to work within the WebView!!
wvUltimo.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
// Log.d(TAG + " 185", "Load Status: " + progress);
if (progress == 100) {
ImageView ivCoolSplash = view.findViewById(R.id.iv_your_splash_screen_id);
ivCoolSplash.setVisibility(View.GONE);
}
}
});
...
this guarantees that you have direct access to your splash screen from within your WebView and thus can deactivate or hide it instantly when the web page you requested is ready to be viewed.

This can be done with the use of WebViewClient() to the webview.
Reference : How can I know that my WebView is loaded 100%?

Related

Javascript on Android WebView not working

I have some issues with Android WebView and Javascript.
Some of customers of app said that WebView on app is not showing anything.
As I checked - its probably not showing javascript at all (whole webpage is loaded in javascript by react).
That my code:
public void setupWebView(WebView accessWebView) {
accessWebView.setWebViewClient(new WebViewClient() {
#SuppressWarnings("deprecation")
#Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
handleRedirect(accessWebView);
return true;
}
});
accessWebView.getSettings().setJavaScriptEnabled(true);
accessWebView.getSettings().setDomStorageEnabled(true);
accessWebView.loadUrl(URL);
(I have to use WebViewClient, not WebChromeClient, because of the redirect handling)
Is there anything possible to change so the javascript will load on EVERY device with Android +5.0?
Is it possible that updating WebView on device will help some users?
You need to use setWebChromeClient to enable javascript in your WebView. But don't worry, you can use both setWebChromeClient and setWebViewClient in the same time. Just like in official docs:
// Let's display the progress in the activity title bar, like the
// browser app does.
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("https://developer.android.com/");
https://developer.android.com/reference/android/webkit/WebView.html

WebViewClient not calling shouldOverrideUrlLoading

The problem is rather simple.
In the application we want to keep track of the current url being displayed. For that we use shouldOverrideUrlLoading callback from the WebViewClient by saving the url into a class field for every update. Here is the relevant code:
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
mCurrentUrl = url;
// If we don't return false then any redirect (like redirecting to the mobile
// version of the page) or any link click will open the web browser (like an
// implicit intent).
return false;
}
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
...
}
});
mWebView.loadUrl(mInitialUrl);
However, there is at least one scenario, where the callback never gets triggered and the mCurrentUrl field doesnt get updated.
The url: https://m.pandora.net/es-es/products/bracelets/556000
Last updated url (shouldOverrideUrlLoading never gets called when clicking the product): https://m.pandora.net/es-es/products/bracelets
I have tried with callbacks like onPageStarted(), but the url also gets filtered and there doesn't seem to be an accessible one upstream since its protected code.
Reading android documentation about WebView I found this:
https://developer.android.com/guide/webapps/migrating.html#URLs
The new WebView applies additional restrictions when requesting resources and resolving links that use a custom URL scheme. For example, if you implement callbacks such as shouldOverrideUrlLoading() or shouldInterceptRequest(), then WebView invokes them only for valid URLs.
But still doesnt make sense since the above url is generic and should meet the standard.
Any alternative or solution to this?
When you click a product on that web page, it loads the new content in with JavaScript and updates the visible URL in the address bar using the HTML5 History APIs.
From the above MDN article:
This will cause the URL bar to display http://mozilla.org/bar.html, but won't cause the browser to load bar.html or even check that bar.html exists.
These are sometimes called single-page applications. Since the actual loaded page doesn’t change, the WebView callback for page loads isn’t called.
In case you know precisely what kind of HTTP request you want to intercept, you could use the shouldInterceptRequest callback that gets called for each request. It’s likely that the web application loads some data from an API, for example when a product is shown, which you could then detect.
If detecting this isn’t possible, but you’re in control of the web application, you could use the Android JavaScript interface to invoke methods within the Android application directly from the web page.
If you’re not in control of the loaded page, you could still try to inject a local JavaScript file into the web page and observe when the history APIs are used, then call methods in your Android application over the JS interface. I tried observing these events in Chrome with the method described in the previous link and it seems to work fine.
Maybe this helps someone, although the signature in the question is correct, but Android Studio suggests the following method signature:
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
which then never called. It took me a while to notice that the right signature is:
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Sorry if this not 100% fit the question, but I believe this may help someone in the same situation. It's not always easy to notice that the second parameter is different.
Please omit mWebView.getSettings().setDomStorageEnabled(true);
Then again try, if a new url found then will invoke shouldOverrideUrl()
I had the same problem like you, and I've finished with extending of WebViewChromeClient with listening for callback to
public void onReceivedTitle(WebView view, String title)
mWebView.setWebChromeClient(mSWWebChromeClient);
private WebChromeClient mSWWebChromeClient = new WebChromeClient() {
#Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
if (!view.getUrl().equals(mCurrentUrl)) {
mCurrentUrl = view.getUrl();
//make something
}
}
};
For me the problem was below line -
mWebView.getSettings().setSupportMultipleWindows(true);
After removing it shouldOverrideUrlLoading was being called.
after stumbling on this problem and searching for solutions, I've found the one that worked perfectly for me
https://stackoverflow.com/a/56395424/10506087
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
// your code here
super.doUpdateVisitedHistory(view, url, isReload)
}
Another approach you can try: Catch the url by javascript side. Initialize your webView with this:
webView.addJavascriptInterface(new WebAppInterface(getActivity()), "Android");
After page is completely loaded (You can use an algorithm to check this like this https://stackoverflow.com/a/6199854/4198633), then:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("(function() {return window.location.href;})", new ValueCallback<String>() {
#Override
public void onReceiveValue(String url) {
//do your scheme with variable "url"
}
});
} else {
webView.loadUrl("javascript:Android.getURL(window.location.href);");
}
And declare your WebAppInterface:
public class WebAppInterface {
Activity mContext;
public WebAppInterface(Activity c) {
mContext = c;
}
#JavascriptInterface
public void getURL(final String url) {
mContext.runOnUiThread(new Runnable() {
#Override
public void run() {
//do your scheme with variable "url" in UIThread side. Over here you can call any method inside your activity/fragment
}
});
}
}
You can do something like that to get url, or anything else inside the page.
Add
webView.getSetting().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
then shouldOverrideUrl will be triggered.
onProgressChanged is always triggered when reloading, loading new page with userclick or XmlHttpRequest.
Compare the URL of previous load and the current load, you'll know it's reloading or loading a new page. This works perfect in my single page Web App.
First declare a global variable to store last URL.
String strLastUrl = null;
Then override onProgressChanged(WebView view, int progress)
mWebView.setWebChromeClient(new MyWebChromeClient(){
#Override
public void onProgressChanged(WebView view, int progress) {
if (progress == 100) {
//A fully loaded url will come here
String StrNewUrl = view.getUrl();
if(TextUtils.equals(StrNewUrl,strLastUrl)){
//same page was reloaded, not doing anything
}else{
//a new page was loaded,write this new url to variable
strLastUrl = StrNewUrl;
//do your work here
Log.d("TAG", "A new page or xhr loaded, the new url is : " + strLastUrl);
}
}
super.onProgressChanged(view, progress);
}
});
I've also tried above solutions, but most of them have issue in my case:
doUpdateVisitedHistory sometimes can not return correct url after "#" made by XmlHttpRequest.
My case is a single page web App. The web App uses javascript with
xhr to display new page when user click an item. For example, user is
currently at http://example.com/myapp/index.php , after clicking, the
browser url becomes
http://example.com/myapp/index.php#/myapp/query.php?info=1, but in
this case, doUpdateVisitedHistory returns
http://example.com/myapp//myapp/
onReceivedTitle doesn't work in my case because the response retrieved by XMLHttpRequest does not have <title></title> tag.
The JavascriptInterface method also works, but I'm afraid it will cause
security related issues with javascript.
public class AndroidMobileAppSampleActivity extends Activity {
/** Called when the activity is first created. */
String mCurrentUrl="";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView mWebView = (WebView) findViewById(R.id.mainWebView);
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
mWebView.setWebViewClient(new MyCustomWebViewClient());
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
mWebView.loadUrl("https://m.pandora.net/es-es/products/bracelets/556000");
}
private class MyCustomWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
mCurrentUrl = url;
Log.i("mCurrentUrl",""+mCurrentUrl);
view.loadUrl(url);
return true;
}
}
}
try this one...

WebView doesn't show content after onPause

I have a WebView inside a RecyclerView
I configured the WebViewClient to run onPuase() when page finished loading.
The problem is that some websites (like IMDB) are not viewed, unless I scroll the page down/up, or if the page in stored in cache.
Not working code:
getWebview().setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.onPause();
}
});
If I delay the onPause, it works (delay time differs between different devices)
getWebview().setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(final WebView view, String url) {
super.onPageFinished(view, url);
getWebview().postDelayed(new Runnable() {
#Override
public void run() {
getWebview().onPause();
}
}, 5000);
}
});
I also tried getWebview().postInvalidateDelayed() and getWebview().requestLayout().
Is there anyway to force the webview to display the loaded content, or simulate whatever happens when I scroll the page?
I use Lollipop with Android System WebView 43.0.2357.121
If you try to debug or put some logs in onPagefinshed() method, you will come to know that Webview's onPauuse() will call 2-3 times before site the
loads completely in case of URL redirecting.

getTitle() using web URL, rather than webView.getTitle();

I am trying to get the title of a webpage in a webView, I cannot just call webView.getTitle(); because even in onPageStarted() the WebView has not yet received the title. I do however already have the URL for the webpage that being loaded, so if theres something like getTitle(url); That would be exactly what I want.
Just using the URL, you'd have to load the document over the network, parse it, and then take the title—which you probably don't want to do yourself.
I think what you actually need is this: set a custom WebViewClient for your WebView, and implement onPageFinished() for that; the WebView instance passed to that method has title set. This answer has a complete example.
Its not the best way but it works(Its about 70% faster):
web.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress)
{
if(progress > 30 && progress < 41){
// using boolean:lock to call this method once everytime
if(!lock){
// get the Title by web.getTitle();
lock = true;
}
}
if(progress > 40){ lock = false; }
}});
OR another way(Even Faster) :
web.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress)
{
if(OldPageTitle != web.getTitle()){//getTitle has the newer Title
// get the Title
OldPagerTitle = web.getTitle();
}
}});

Android : click link in a page within web view

I have included a web application within android web view , and there is a link in the webpage which opens some other site , when the link is clicked it works fine for the first click, however when clicked for the second time the website is not found ,
the code is :
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("some site ")) {
Intent i = new
Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
return true;
} else {
view.loadUrl(url);
return false;
}
}
#THelper and #mikegr, thanks for the reply,
Actually in my case i have a modal panel (JSF) in my web application which contains some buttons, on clicking the button i am opening some other site using javascript window.open() method which works fine in desktop browser, however, when i wrap this web application within android webview, everything works fine except when i first click this button i'm able to open the other site using the external browser, however on second click the webview tries to open this othersite within the webview instead of the external browser and i get website not found with the entire URL of the other site, this happens even when i logout and login again as the application launched is still running.
also in my case after sometime when the application is idle i get the black screen.
i surfed through the net and found simillar issue but that didn't help either , here is the link:
http://groups.google.com/group/android-for-beginners/browse_thread/thread/42431dd1ca4a9d98
handling links in a webview ,
any help and ideas would be very helpful for me, this is taking too long for me,
since i'm trying to display my web application in the web view, i have only one activity, which contains code like this
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// so that when launcher is clicked while the application is
// running , the application doesn't start from the begnining
} else {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
// Show the ProgressDialog on this thread
this.progressDialog = ProgressDialog.show(this, "Pleas Wait..", "Loading", true);
browser = (WebView) findViewById(R.id.webview);
browser.getSettings().setJavaScriptEnabled(true);
browser.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
Log.i(TAG, "Finished loading URL: " +url);
if (progressDialog.isShowing()) {
progressDialog .dismiss();
}
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("some site")) {
Intent i = new
Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
return true;
} else {
view.loadUrl(url);
return true;
}
}
});
browser.loadUrl("mysite");
}
}
I had the experience that shouldOverrideUrlLoading() is not called in certain circumstances.
There are a few bugs about this topic on
http://code.google.com/p/android/issues
like bug number 15827, 9122, 812, 2887
As a workaround try to add the method onPageStarted() and check if you get this call. For me this method is always called even if shouldOverrideUrlLoading() was not called before.
onPageStarted worked for me. Had to tweak it a bit, as that method is called when the webview is first rendered too, and I wanted to only execute it on the onClick of the banner's javascript.
I was simulating a banner with a custom page, so when the ad.html was being rendered, I avoided the startActivity. Otherwise, it launches the new browser window.
WebViewClient newWebClient = new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
if(!url.equals("http://xxxx.ad.html"))
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
};
Try to append the System parameter pattern in url before you load that, something like.
String url = "http://xxxx.ad.html?t="+System.nanoTime();
and in your shouldOverrideUrlLoading() method remove the query part (in a very first line).
int idx;
if ((idx = url.indexOf("?")) != -1) {
url = url.substring(0, idx);
}

Categories

Resources