I want to make an android application which has a webview layout. This is the criteria of my application:
The first time the application starts, webview loads an url (maybe facebook, google, etc..)
webview.loadUrl("http://www.google.com");
After it loads the url, the application saves the loaded url to HTML View (file.htm) in a "specific place" in android's internal storage. So, let's say i open "google.com", the application saves the google's web page to HTML file (let's say the filename, "google.htm"), and when i go to that "specific place" and click the "google.htm" file, it shows the google web page using android's HTML Viewer.
When the application starts again, or simply say the application loads the url again (in this case, "google.com") , it doesn't take from the "google.com" page BUT it takes from the "google.htm" file on the internal storage android. So from the user's view, that application can still load webpages, even though it's not connected to internet.
To make it simple,
Application Start -> Go to the specified url -> Check the storage
IF there's the specified url HAS the HTML file in the storage, then load from the storage
ELSE it loads the url from the web.
Can anyone help me with the code and explanation? I really appreciate it. Thanks guys :D
You can use a Javascript interface for the WebView to return the entirety of the HTML source when the page is finished loading. To do this, you'll need to assign your own WebViewClient to the WebView.
To do this, use something similar to the following in your Activity class -- Make sure your Activity implements Observer:
public void onCreate(Bundle savedInstanceState) {
// ...
webView.setWebViewClient(new MyWebViewClient());
HtmlJSInterface htmlJSInterface = new HtmlJSInterface();
webView.addJavascriptInterface(htmlJSInterface, "HTMLOUT");
htmlJSInterface.addObserver(this);
// ...
}
// Called when our JavaScript Interface Observables are updated.
#Override
public void update(Observable observable, Object observation) {
// Got full page source.
if (observable instanceof HtmlJSInterface) {
html = (String) observation;
onHtmlChanged();
}
}
private void onHtmlChanged() {
// Do stuff here...
}
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
// When each page is finished we're going to inject our custom
// JavaScript which allows us to
// communicate to the JS Interfaces. Responsible for sending full
// HTML over to the
// HtmlJSInterface...
isStarted = false;
isLoaded = true;
timeoutTimer.cancel();
view.loadUrl("javascript:(function() { "
+ "window.HTMLOUT.setHtml('<html>'+"
+ "document.getElementsByTagName('html')[0].innerHTML+'</html>');})();");
}
}
}
Then, you're going to want to create the HtmlJSInterface class, as such:
public class HtmlJSInterface extends Observable {
private String html;
/**
* #return The most recent HTML received by the interface
*/
public String getHtml() {
return this.html;
}
/**
* Sets most recent HTML and notifies observers.
*
* #param html
* The full HTML of a page
*/
public void setHtml(String html) {
this.html = html;
setChanged();
notifyObservers(html);
}
}
Related
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...
I am loading an URL in webview. i.e. first I am loading a login page in a Webview. Then after user type username and password and tap login button in webview page, it shows the particular session key for that logon in the webview. Now I want to read that session key and hide that information from user. Is anybody has the idea how to do that?
Thanks.
The same question has been answered multiple times. And all the answers refer to the this post. Though this post has warnings in the end, I could not find any other eaiser way to achive what tou are looking for.
Below is the copy-paste of the code from this post.
final Context myApp = this;
/* An instance of this class will be registered as a JavaScript interface */
class MyJavaScriptInterface
{
#JavascriptInterface
#SuppressWarnings("unused")
public void processHTML(String html)
{
// process the html as needed by the app
}
}
//Edit 1 start
final ProgressDialog pd = ProgressDialog.show(OnlinePaymentActivity.this, "", "Please wait, your transaction is being processed...", true);
//Edit 1 end
final WebView browser = (WebView)findViewById(R.id.browser);
/* JavaScript must be enabled if you want it to work, obviously */
browser.getSettings().setJavaScriptEnabled(true);
/* Register a new JavaScript interface called HTMLOUT */
browser.addJavascriptInterface(new MyJavaScriptInterface(), "HTMLOUT");
/* WebViewClient must be set BEFORE calling loadUrl! */
browser.setWebViewClient(new WebViewClient() {
/////Edit 2 start
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon)
{
If(browser.getVisibility() == View.VISIBLE)
{
browser.setVisibility(View.GONE);
}
pd.show();
}
///// Edit 2 end
#Override
public void onPageFinished(WebView view, String url)
{
//Edit 3 start
If(browser.getVisibility() == View.GONE)
{
browser.setVisibility(View.VISIBLE);
}
pd.dismiss();
// Edit 3 end
/* This call inject JavaScript into the page which just finished loading. */
browser.loadUrl("javascript:window.HTMLOUT.processHTML('<head>'+document.getElementsByTagName('html')[0].innerHTML+'</head>');");
}
});
/* load a web page */
browser.loadUrl("http://lexandera.com/files/jsexamples/gethtml.html");
I found a way of doing this. i.e. First I am reading the token in the body text and once the token found, make the webView to invisible. Your suggestion is not applicable for me as sometimes I want to show the login page. i.e. if the user is not login to the particular account, we should show the web view. Once they return the token after login, webview should be hidden.
I have a webview containing a form which has both send and cancel buttons. The webview opens in a dialog and I'd like this to close upon either successful form submission or cancellation. The buttons are part of the webview's HTML. The form exists on a third party server that I have no control over. The send button reloads the parent page, and the cancel button closes the form on the originating site. I'm using an HTML parser to display just the form and buttons.
Is there a way to accomplish this?
This worked for me
/**
* Custom web client to let us override behaviour when the edit form has been submitted
*/
private WebViewClient getWebviewClient() {
return new WebViewClient() {
/**
* let's finish this activity once the form has been saved and a message has been display
*/
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (url.contains(DONE_URL)) {
displayDialogToFinishActivity();
}
}
};
}
WebViewClient rclient = new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
return true;
}
#Override
public void onLoadResource(WebView view, String url){
finish();
}
};
webview.setWebViewClient(rclient);
This seems to work.
I want a HTML/javascript application, running in a WebView to make AJAX calls that are handled by the Java code.
Ideal would be to just intercept the call (easy, just use shouldOverrideUrlLoading()) and 'return' some data.
However, I don't find a way to 'return' a response to the WebView, other than calling a javascript function using loadUrl().
This will not work for me, as the HTML/javascript app is a drop-in application which I don't control. As far as the HTML/javascript app concerns, it just does an AJAX call and receives some data back.
Any thoughts on this?
Good news everyone: With API level 11, they put in the shouldInterceptRequest method into the WebViewClient class. It also fires on requests the application inside the WebView triggers. You can override it as follows:
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url)
{
if (magicallyMatch(url))
return new WebResourceResponse("application/json", "utf-8", magicallyGetSomeInputStream());
return null;
}
From the Android Reference:
public WebResourceResponse shouldInterceptRequest (WebView view, String url)
Since: API Level 11
Notify the host
application of a resource request and
allow the application to return the
data. If the return value is null, the
WebView will continue to load the
resource as usual. Otherwise, the
return response and data will be used.
NOTE: This method is called by the
network thread so clients should
exercise caution when accessing
private data.
Parameters
view The WebView that is requesting the resource.
url The raw url of the resource.
Returns
A WebResourceResponse containing the
response information or null if the
WebView should load the resource
itself.
Also check WebResourceResponse.
Hope this helps.
You can use the JavascriptInterface to intercept the AJAX calls along with JQuery methods ajaxStart and ajaxComplete in following way:
// our JavascriptInterface
public class AjaxHandler {
private static final String TAG = "AjaxHandler";
private final Context context;
public AjaxHandler(Context context) {
this.context = context;
}
public void ajaxBegin() {
Log.w(TAG, "AJAX Begin");
Toast.makeText(context, "AJAX Begin", Toast.LENGTH_SHORT).show();
}
public void ajaxDone() {
Log.w(TAG, "AJAX Done");
Toast.makeText(context, "AJAX Done", Toast.LENGTH_SHORT).show();
}
}
And here is how the AjaxHandler is used in Activity:
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private WebView webView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// get web view
webView = (WebView) findViewById(R.id.web);
// configure web view
final WebSettings webSettings = webView.getSettings();
webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("http://foo.com");
// add javascript interface
webView.addJavascriptInterface(new AjaxHandler(this), "ajaxHandler");
// override onPageFinished method of WebViewClient to handle AJAX calls
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.loadUrl("javascript:$(document).ajaxStart(function (event, request, settings) { " +
"ajaxHandler.ajaxBegin(); " + // Event called when an AJAX call begins
"});");
view.loadUrl("javascript:$(document).ajaxComplete(function (event, request, settings) { " +
"ajaxHandler.ajaxDone(); " + // Event called when an AJAX call ends
"});");
});
}
}
The main idea is taken from here and presented with some tweaks.
Although its a little late to submit an answer but hope this helps others as well!
In the end I used the method described in the question anyway;
I intercepted some specific requests like: foo://bar/get/1?callback=foobar then parsed the URL and called a javascript callback function with the actual data.
The callback function was defined in the query-part of the URL, so in the above example that would be: foobar();
Calling that function was easy, just use: yourWebView.loadUrl("javascript:foobar('somedata')");
I have a WebViewClient attached to my WebView like so:
webView.setWebViewClient(new MyWebViewClient());
Here is my implementation of MyWebViewClient:
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
webView.loadUrl(url);
return true;
}
}
I give the WebView a URL to load via loadUrl(). If I have a link (a href...) in the page, my shouldOverrideUrlLoading method is called and I can intercept the link click.
However, if I have a form whose method is POST, the shouldOverrideUrlLoading method is not called.
I noticed a similar issue here: http://code.google.com/p/android/issues/detail?id=9122 which seems to suggest overriding postUrl in my WebView. However, this API is only available starting from API level 5.
What can I do if I'm on API level 4? Is there any other way to intercept form posts?
This is known issue, that shouldOverrideUrlLoading don't catch POST. See http://code.google.com/p/android/issues/detail?id=9122 for details.
Use GET! I personally tried using POST, because I expected some limitation of GET parameters (i.e. length of URL), but I just successfully passed 32000 bytes through GET locally without any problems.
Do you really need to use a POST? If you want to handle formdata locally, why not have a piece of javascript handle your form and interface with "native" java code using addJavascriptInterface. E.g.
WebView engine = (WebView) findViewById(R.id.web_engine);
engine.getSettings().setJavaScriptEnabled(true);
engine.addJavascriptInterface(new MyBridge(this), "bridge");
engine.loadUrl(...)
Your bridge can be any class basically and you should be able to access its methods directly from javascript. E.g.
public class MyBridge {
public MyBridge(Context context) {
// ...
}
public String doIt(String a, String b) {
JSONArray result = new JSONArray();
result.put("Hello " + a);
result.put("Hello " + b);
return result.toString();
}
Your html / javascript could look like:
<script type="text/javascript">
$("#button").click(function() {
var a = $("#a").val();
var b = $("#b").val();
var result=JSON.parse(bridge.doIt(a, b));
// ...
}
</script>
<input id="a"><input id="b"><button id="button">click</button>
I think you can override onLoadResource(WebView view, String url) from WebViewClient. This function is Added in API LEVEL 1.
This function is called when WebView will load the resource specified by the given url. Resource include js, css, iframe embeded url. Code example like this:
#Override
public void onLoadResource(WebView view, String url) {
if (url.indexOf("http://www.example.com") != -1 && view != null) {
// open url in default browser
view.stopLoading();
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}