so a quick overview of what I'm doing
I am using Android Webview to Render JavaScript and then reading the HTML from the javascript to parse it.
I am currently having trouble with retrieving the HTML from a website called Sport Chek.
Here is the code for my SportChekSearch class:
public class SportChekSearch extends SearchQuery{
public Elements finalDoc;
private ArrayList<Item> processed;
private final Handler uiHandler = new Handler();
public int status = 0;
//This basically is just so that the class knows which Activity we're working with
private Context c;
protected class JSHtmlInterface {
#android.webkit.JavascriptInterface
public void showHTML(String html) {
final String htmlContent = html;
uiHandler.post(
new Runnable() {
#Override
public void run() {
Document doc = Jsoup.parse(htmlContent);
}
}
);
}
}
/**
* Constructor method
* #param context The context taken from the webview (So that the asynctask can show progress)
*/
public SportChekSearch(Context context, String query) {
final Context c = context;
try {
final WebView browser = new WebView(c);
browser.setVisibility(View.INVISIBLE);
browser.setLayerType(View.LAYER_TYPE_NONE, null);
browser.getSettings().setJavaScriptEnabled(true);
browser.getSettings().setBlockNetworkImage(true);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
browser.getSettings().setLoadsImagesAutomatically(false);
browser.getSettings().setGeolocationEnabled(false);
browser.getSettings().setSupportZoom(false);
browser.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
browser.addJavascriptInterface(new JSHtmlInterface(), "JSBridge");
browser.setWebViewClient(
new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url) {
browser.loadUrl("javascript:window.JSBridge.showHTML('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');");
}
}
);
browser.loadUrl("https://www.sportchek.ca/search.html#q=" + query.replaceAll(" ", "+") + "&lastVisibleProductNumber=3");
browser.loadUrl(browser.getUrl());
final String link = browser.getUrl();
new fetcher(c).execute(link);
}
catch(Exception e){
e.printStackTrace();
}
//Get the link from the WebView, and save it in a final string so it can be accessed from worker thread
}
/**
* This subclass is a worker thread meaning it does work in the background while the user interface is doing something else
* This is done to prevent "lag".
* To call this class you must write fetcher(Context c).execute(The link you want to connect to)
*
*/
class fetcher extends AsyncTask<String, Void, Elements> {
Context mContext;
ProgressDialog pdialog;
public fetcher(Context context) {
mContext = context;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
pdialog = new ProgressDialog(mContext);
pdialog.setTitle(R.string.finding_results);
pdialog.setCancelable(false);
pdialog.show();
}
//This return elements because the postExecute() method needs an Elements object to parse its results
#Override
protected Elements doInBackground(String... strings) {
//You can pass in multiple strings, so this line just says to use the first string
String link = strings[0];
//For Debug Purposes, Do NOT Remove - **Important**
System.out.println("Connecting to: " + link);
try {
doc = Jsoup.connect(link)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36")
.timeout(10000)
.get();
finalDoc = doc.select("body section.product-grid-wrapper");
System.out.println(finalDoc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return finalDoc;
}
#Override
protected void onPostExecute(Elements result) {
//This line clears the list of info in the Search activity
//I should probably be using a getter method but adapter is a static variable so it shouldn't matter
//parse seperates document into elements
//crunch results formats those elements into item objects
//I am saving the result of this to an ArrayList<Item> called "processed"
processed = crunchResults(result);
//For debug purposes, do NOT remove - **Important**
System.out.println(processed.size() + " results have been crunched by Sport Chek.");
//Adds all of the processed results to the list of info in Search activity
ClothingSearch.adapter.addAll(processed);
//For debug purposes, do NOt remove - **Important
System.out.println("Adapter has been notified by Sport Chek.");
//Closes the progress dialog called pdialog assigned to the AsyncTask
pdialog.dismiss();
ClothingSearch.adapter.notifyDataSetChanged();
SearchQueueHandler.makeRequest(mContext, processed, SearchQueueHandler.CLOTHING_SEARCH);
}
}
public ArrayList<Item> crunchResults(Elements e){
ArrayList<Item> results = new ArrayList<Item>();
try {
for (int i = 0; i < e.size(); i++) {
Element ele = e.get(i);
String link = "https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href");
System.out.println("https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href"));
String title = ele.select(" span.product-title-text").text();
String pricestring = ele.select(" span.product-price__wrap").text();
price = Double.parseDouble(pricestring.substring(pricestring.lastIndexOf("$")));
System.out.println(pricestring);
//*******************************************
String store = "Sport Chek";
//Adds the formatted item to an ArrayList of items
results.add(new Item(title, store, price, link));
//Prints the object's to String to console
//For debug purposes, do NOT remove - **Important
System.out.println(results.get(i).toString());
}
} catch (Exception a){
a.printStackTrace();
}
return results;
}
public int getStatus(){
return status;
}
}
The two relevant methods are doInBackground in my AsyncTask and the crunchResults method.
Here is the result I get from using Ctrl+Shift+I on the actual website (Desired Result):
But when running the above code and using a println here is the result that I get for the tag section class="product-grid-wrapper" :
<section class="product-grid-wrapper">
<ul data-module-type="SearchProductGrid" class="product-grid__list product-grid__list_quickview">
<!-- #product-grid__item-template -->
</ul>
</section>
Can anyone help me figure out why I am not getting my desired result?
All help is appreciated
EDIT: for this specific search that the println data was collected from, the link was https://www.sportchek.ca/search.html#q=men+coat&lastVisibleProductNumber=3
It looks like what you are actually getting is the actual html sent by the server, and that your 'desired result' is what the DOM looks like after the JavaScript runs.
Your 'actual' is what I see if I use "View Source" in Chrome, while your "desired result" is what I see if I use Chrome's DOM inspector.
On further inspection, I see that you are not actually getting the HTML from the browser, you are (indirectly) using JSoup's Connection object to get the HTML directly. Unfortunately, that's not going to run the Javascript.
Instead, you're going to have to get the HTML from the WebView after the JavaScript runs. For a possible way to do that, see How do I get the web page contents from a WebView?
Then, you give the HTML that you get from that to JSoup with
Jsoup.parse(html);
Related
I want to implement a mechanism in a custom webview client (without JavaScript injection) that can block ads. Is a way I can catch ads and replace them with other ads from a trusted source?
Thanks
In your custom WebViewClient, you can override the function shouldInterceptRequest(WebView, WebResourceRequest).
From Android docs:
Notify the host application of a resource request and allow the application to return the data.
So the general idea is to check if the request is coming from an ad URL (plenty of black list filters out there), then return a "fake" resource that isn't the ad.
For a more in depth explanation plus an example, I recommend checking out this blog post.
To implement this, you have two options:
Use Javascript injected code to do this (which you explicitely said, don't want)
In WebView, instead of "http://example.com" load "http://myproxy.com?t=http://example.com" (properly escaped, of course) and setup "myproxy.com" to be a proxy which will fetch the upstream page (given in "t" query parameter, or in any other way) and replace ads with the trusted ones before sending response to the client. This will be pretty complex, though, because ads can be in many forms, they're usually Javascript injected themselves and you'd probably need to rewrite a lot of URL's in the fetched HTML, CSS and JS files etc.
I made a custom WebViewClient like:
public class MyWebViewClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) { }
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith(".mp4")) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), "video/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
return true;
} else if (url.startsWith("tel:") || url.startsWith("sms:") || url.startsWith("smsto:")
|| url.startsWith("mms:") || url.startsWith("mmsto:")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
private Map<String, Boolean> loadedUrls = new HashMap<>();
#SuppressWarnings("deprecation")
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
boolean ad;
if (!loadedUrls.containsKey(url)) {
ad = AdBlocker.isAd(url);
loadedUrls.put(url, ad);
} else {
ad = loadedUrls.get(url);
}
return ad ? AdBlocker.createEmptyResource() :
super.shouldInterceptRequest(view, url);
}
}
And created an AdBlocker class like:
public class AdBlocker {
private static final Set<String> AD_HOSTS = new HashSet<>();
public static boolean isAd(String url) {
try {
return isAdHost(getHost(url));
} catch (MalformedURLException e) {
Log.e("Devangi..", e.toString());
return false;
}
}
private static boolean isAdHost(String host) {
if (TextUtils.isEmpty(host)) {
return false;
}
int index = host.indexOf(".");
return index >= 0 && (AD_HOSTS.contains(host) ||
index + 1 < host.length() && isAdHost(host.substring(index + 1)));
}
public static WebResourceResponse createEmptyResource() {
return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream("".getBytes()));
}
public static String getHost(String url) throws MalformedURLException {
return new URL(url).getHost();
}
}
And use this WebViewClient in your oncreate like:
webview.setWebViewClient(new MyWebViewClient());
I use WebView to show data, the data gets augmented via JS function. It works fine until the device gets rotated. Using ViewModel to keep the page data and restore it after a config change seemed to be the right idea but I ran into problems, the page didn't get restored. I pared down my code to a minimalist example, included here (sans the XML layout)
DataStore class to store the data, overriding ViewModel, simply store some strings in a list
package com.automationce.labyrinth.webview;
import android.arch.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
/* The idea is to use ViewModel to store page data
* so it can be restored on device rotate/split view */
public final class DataStore extends ViewModel {
public List<String> records = new ArrayList<>();
public DataStore() {
super();
}
}
Simple web view class, overriding WebView
package com.automationce.labyrinth.webview;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.webkit.WebView;
public final class SimpleWebView extends WebView {
// Use to keep and restore data between config changes
// (not related to browser history by any means)
private DataStore dataStore;
public SimpleWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(Color.LTGRAY);
getSettings().setJavaScriptEnabled(true);
setWebContentsDebuggingEnabled(true);
// Define style, write JS function(s)
String content = "<!DOCTYPE html><html><head>" +
"<style>" +
"span { font-size: " +
0.875 +
"em; } " +
"</style>" +
"</head><body id=\"body\">" +
jsFunctions() + "</body></html>";
loadDataWithBaseURL(null, content, "text/html", "utf-8", null);
}
// JS function(s) as written to the page
private String jsFunctions() {
return "<script>" +
"function append (string) { " +
"var body = document.getElementById('body');" +
"var block = document.createElement('span');" +
"var text = document.createTextNode(string);" +
"var br = document.createElement('br'); " +
"block.appendChild(text);" +
"block.appendChild(br); " +
"body.appendChild(block);" +
"block.scrollIntoView();" +
" }" +
"</script>";
}
public void setHistory (final DataStore storedData) {
dataStore = storedData;
}
// Restore page from DataStore data
public void restore() {
for(String record : dataStore.records) {
evaluateJavascript("append ('" + record + "');", null);
}
}
// Append new data to existing page
public void append(final String string) {
dataStore.records.add(string);
evaluateJavascript("append ('" + string + "');", null);
}
}
And finally the MainActivity itself
package com.automationce.labyrinth.webview;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private SimpleWebView pageView;
private Button appendButton;
private int counter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DataStore storedData = ViewModelProviders.of(this).get(DataStore.class);
appendButton = findViewById(R.id.button);
pageView = findViewById(R.id.webView);
pageView.setHistory(storedData);
pageView.restore();
appendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
append();
}
});
}
private void append() {
counter++;
pageView.append("Some kind of new text " + counter);
}
}
The idea was to preserve the data in ViewModel and restore it during creation. It took me a while to figure why it doesn't work. DataStore does get preserved during rotation (I can see all the history data in the DataStore object) but although I see the restore code being executed, it never gets rendered in SimpleWebView.
The reason is that while JavaScript function does get correctly written to the page, I am calling restore() from MainActivity.OnCreate before the page actually renders. The JS function gets called and quickly ignored because the page, in reality, doesn't exist as far as WebView is concerned (I think). Surprisingly, this actually worked on the majority of simulators I use - now I think it shouldn't and I have no clue why it did - until I ran it on a physical device (Galaxy S7), where the restore was ignored
My solution was to give my SimpleWebView a new WebViewClient and override its OnPageFinished method and in it restore the content of the page after the page finishes loading. SimpleView constructor ('firstView' boolean flag in the code below gets invalidated after the view reloads for the first time after the device rotation):
public SimpleWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(Color.LTGRAY);
getSettings().setJavaScriptEnabled(true);
setWebContentsDebuggingEnabled(true);
setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.v("PAGE", "finished");
if (firstView) {
((SimpleWebView)view).restore();
}
}
});
.
.
The web page now gets reconstructed from the persistent data after device rotation/split window. It seems to work well, without any noticeable load delay (I am dealing with relatively small data set now).
I have looked for solutions, I really don't want to save and restore a String content of the whole page in OnSaveInstanceState / onRestoreInstanceState because in this case, it won't do me any good - I'll have the data but I still need to restore it after the page gets rendered
Words of wise I seek from the collective:
Is my analysis correct?
Are there better ways of handling this?
Is there something (an unpleasant production code surprise, etc. - I am pretty new to Android) for which I should be watching out?
Thanks, Jiri
For educational reasons I am learning about parsing and scrapping HTML content.
I saw a lot of questions and answers about retrieving the html content displayed in a webview.
My Problem is, that I am not able to get the whole html, how I suppose it should look like. When I inspect the URL in Safari all Items can be located in the HTML, but when I load the HTML of the same URL from webview there are missing items.
My code right now:
`
private WebView wv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
wv = (WebView) findViewById(R.id.webView);
wv.getSettings().setJavaScriptEnabled(true);
wv.addJavascriptInterface(new LoadListener(), "HTMLOUT");
}
#Override
protected void onStart() {
super.onStart();
wv.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
if(wv.getProgress() == 100) {
view.loadUrl("javascript:window.HTMLOUT.processHTML(document.documentElement.innerHTML);");
}
}
});
wv.loadUrl("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#");
}
class LoadListener{
#JavascriptInterface
public void processHTML(String html) {
longInfo(html);
}
}
public static void longInfo(String str) {
if (str.length() > 4000) {
Log.i("DEBUG", str.substring(0, 4000));
longInfo(str.substring(4000));
} else
Log.i("DEBUG", str);
}`
Usuallay I use Jsoup for connecting and parsing HTML, but in this case the webpage is using CloudFlare and I was not able to succesfully load the page HTML.
What I found out is that every Listitem of the webpage which includes the <span class="hot"></span> tag is not loaded in my code right now.
What am I missing ?
UPDATE1
The hint of F.Klein did it!
I really do not understand why setting the user agent as follows:
wv.getSettings().setUserAgentString("Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13");
changed the retrieved HTML as I supposed it to be. The shown content in the webview was before and after setting the user agent the same.
UPDATE2
I have been to early pleased...changing the user agent ended up in missing items to show up but other items which were before loaded now are gone??? I donĀ“t get the problem.
As previously guessed, the mobile version of the page seems to be different from the default version: with the mobile version there are 50 divelements with the class box-productfound while the default version finds 70 elements.
Fix
String userAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36";
wv.getSettings().setUserAgentString(userAgent);
Your mentioned user-agent (Update 1) is one for the mobile version, hence the same result as without setting a user-agent (defaulting to mobile version on android).
Alternative without WebView using Rhino (to calculate the challenge to acquire the access cookie):
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new BackgroundTask().execute();
}
class BackgroundTask extends AsyncTask<Void,Void,Void>{
#Override
protected Void doInBackground(Void... voids) {
scrapePage();
return null;
}
}
private void scrapePage() {
Map<String, String> cookies;
final String referrer = "http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=0";
try {
Connection.Response res = Jsoup.connect("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#")
.followRedirects(true).header("Connection", "keep-alive").userAgent(userAgent)
.ignoreHttpErrors(true).header("host", "hdfilme.tv").referrer(referrer).method(Connection.Method.GET)
.execute();
cookies = res.cookies();
Document doc = Jsoup.parse(res.body());
String[] scriptlines = doc.select("script").toString().split("\n");
StringBuilder builder = new StringBuilder();
builder.append("var a={};");
for (String line : scriptlines) {
if (line.contains("var s,t,o,p,b,r,e,a,k,i,n,g")) {
builder.append(line.trim());
builder.append("t=\"hdfilme.tv\"");
}
if (line.contains("a.value = parseInt")) {
builder.append(line.trim());
}
}
int jschl_answer = runRhino(builder.toString());
String jschl_vc = doc.select("input[name=jschl_vc]").first().attr("value");
String pass = doc.select("input[name=pass]").first().attr("value");
Log.e("info","Acquiring access cookies (takes about 4 seconds).");
try {
Thread.sleep(4000); //DDOS protection
} catch (InterruptedException e) {
e.printStackTrace();
}
String requestUrl = "http://hdfilme.tv/cdn-cgi/l/chk_jschl";
res = Jsoup.connect(requestUrl).followRedirects(true).userAgent(userAgent)
.header("Referer", "http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=")
.data("jschl_vc", jschl_vc).ignoreHttpErrors(true).cookies(cookies)
.data("jschl_answer", "" + jschl_answer).data("pass", pass).header("host", "hdfilme.tv")
.method(Connection.Method.GET).timeout(5000).execute();
Log.e("repsonse",res.statusCode() + " - " + res.statusMessage());
cookies.putAll(res.cookies());
builder = new StringBuilder();
builder.append("\tcookies");
for (String cookie : cookies.keySet()) {
builder.append("\n\t\t" + cookie + ":" + cookies.get(cookie));
}
Log.e("cookies", builder.toString());
doc = Jsoup.connect("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#")
.cookies(cookies)
.userAgent(userAgent)
.timeout(10000).referrer(referrer).header("host", "hdfilme.tv").followRedirects(true).get();
builder = new StringBuilder();
builder.append("elements");
for (Element element : doc.select(selector)) {
builder.append("\n"+element.select("a[href]").attr("abs:href"));
}
Log.e("elements","total number of elements="+doc.select(selector).size()+"\n"+builder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
private int runRhino(String jsSource) {
Context rhino = Context.enter(); // org.mozilla.javascript.Context;
// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
Scriptable scope = rhino.initStandardObjects();
rhino.evaluateString(scope, jsSource, "ScriptAPI", 1, null);
NativeObject object = (NativeObject) scope.get("a", scope);
return (int) ((Double) object.get("value") / 1);
}
I've looked in a variety of places for an answer to my query, but nothing has helped me thus far. I'm currently trying to learn Android development, and i'm stuck on how to sort a list alphabetically. I am using this tutorial on how to create the list and have modified parts of the "Albums" page to suit my needs, from albums to artists (i'm still working on this, just wanting the sorting finished before changing it fully). This particular list calls on a file from a HTTP address when the app is accessed to check it for updates.
Here is the code from that particular page, minus all the imports:
public class AlbumsActivity extends ListActivity {
// Connection detector
ConnectionDetector cd;
// Alert dialog manager
AlertDialogManager alert = new AlertDialogManager();
// Progress Dialog
private ProgressDialog pDialog;
// Creating JSON Parser object
JSONParser jsonParser = new JSONParser();
ArrayList<HashMap<String, String>> albumsList;
// albums JSONArray
JSONArray albums = null;
// albums JSON url
private static final String URL_ALBUMS = "http://api.androidhive.info/songs/albums.php";
// ALL JSON node names
private static final String TAG_ID = "id";
private static final String TAG_NAME = "name";
private static final String TAG_SONGS_COUNT = "songs_count";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_albums);
cd = new ConnectionDetector(getApplicationContext());
// Check for internet connection
if (!cd.isConnectingToInternet()) {
// Internet Connection is not present
alert.showAlertDialog(AlbumsActivity.this, "Internet Connection Error",
"Please connect to working Internet connection", false);
// stop executing code by return
return;
}
// Hashmap for ListView
albumsList = new ArrayList<HashMap<String, String>>();
// Loading Albums JSON in Background Thread
new LoadAlbums().execute();
// get listview
ListView lv = getListView();
/**
* Listview item click listener
* TrackListActivity will be lauched by passing album id
* */
lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View view, int arg2,
long arg3) {
// on selecting a single album
// TrackListActivity will be launched to show tracks inside the album
Intent i = new Intent(getApplicationContext(), TrackListActivity.class);
// send album id to tracklist activity to get list of songs under that album
String album_id = ((TextView) view.findViewById(R.id.album_id)).getText().toString();
i.putExtra("album_id", album_id);
startActivity(i);
}
});
}
/**
* Background Async Task to Load all Albums by making http request
* */
class LoadAlbums extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(AlbumsActivity.this);
pDialog.setMessage("Listing Artists...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* getting Albums JSON
* */
protected String doInBackground(String... args) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
String json = jsonParser.makeHttpRequest(URL_ALBUMS, "GET",
params);
// Check your log cat for JSON reponse
Log.d("Albums JSON: ", "> " + json);
try {
albums = new JSONArray(json);
if (albums != null) {
// looping through All albums
for (int i = 0; i < albums.length(); i++) {
JSONObject c = albums.getJSONObject(i);
// Storing each json item values in variable
String id = c.getString(TAG_ID);
String name = c.getString(TAG_NAME);
String songs_count = c.getString(TAG_SONGS_COUNT);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_ID, id);
map.put(TAG_NAME, name);
map.put(TAG_SONGS_COUNT, songs_count);
// adding HashList to ArrayList
albumsList.add(map);
}
}else{
Log.d("Albums: ", "null");
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String file_url) {
// dismiss the dialog after getting all albums
pDialog.dismiss();
// updating UI from Background Thread
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
ListAdapter adapter = new SimpleAdapter(
AlbumsActivity.this, albumsList,
R.layout.list_item_albums, new String[] { TAG_ID,
TAG_NAME, TAG_SONGS_COUNT }, new int[] {
R.id.album_id, R.id.album_name, R.id.songs_count });
// updating listview
setListAdapter(adapter);
}
});
}
}
}
My problem is that I have no real idea where I need to put the Collections.sort command. I have tried in so many places, but cannot get it working. No matter where I put the command, it always sorts in order of ID. This is the full code I have for that:
Collections.sort(params, new Comparator<NameValuePair>() {
#Override
public int compare(NameValuePair art1, NameValuePair art2) {
//here getTitle() method return app name...
return art1.getName().compareToIgnoreCase(art2.getName());
}
});
If I try to have return art1.name.compareToIgnoreCase(art2.name); it comes back with an error that says name cannot be resolved or is not a field. I'm really stumped. I thought a list might be a good way to start learning to develop for Android, but now i'm finding this incredibly hard.
Any help would be greatly appreciated.
I am trying to call some javascript functions sitting in an html page running inside an android webview. Pretty simple what the code tries to do below - from the android app, call a javascript function with a test message, which inturn calls a java function back in the android app that displays test message via toast.
The javascript function looks like:
function testEcho(message){
window.JSInterface.doEchoTest(message);
}
From the WebView, I have tried calling the javascript the following ways with no luck:
myWebView.loadUrl("javascript:testEcho(Hello World!)");
mWebView.loadUrl("javascript:(function () { " + "testEcho(Hello World!);" + "})()");
I did enable javascript on the WebView
myWebView.getSettings().setJavaScriptEnabled(true);
// register class containing methods to be exposed to JavaScript
myWebView.addJavascriptInterface(myJSInterface, "JSInterface");
And heres the Java Class
public class JSInterface{
private WebView mAppView;
public JSInterface (WebView appView) {
this.mAppView = appView;
}
public void doEchoTest(String echo){
Toast toast = Toast.makeText(mAppView.getContext(), echo, Toast.LENGTH_SHORT);
toast.show();
}
}
I've spent a lot of time googling around to see what I may be doing wrong. All examples I have found use this approach. Does anyone see something wrong here?
Edit: There are several other external javascript files being referenced & used in the html, could they be the issue?
I figured out what the issue was : missing quotes in the testEcho() parameter. This is how I got the call to work:
myWebView.loadUrl("javascript:testEcho('Hello World!')");
From kitkat onwards use evaluateJavascript method instead loadUrl to call the javascript functions like below
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("enable();", null);
} else {
webView.loadUrl("javascript:enable();");
}
public void run(final String scriptSrc) {
webView.post(new Runnable() {
#Override
public void run() {
webView.loadUrl("javascript:" + scriptSrc);
}
});
}
I created a nice wrapper to call JavaScript methods; it also shows JavaScript errors in log:
private void callJavaScript(String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if(param instanceof String){
stringBuilder.append("'");
stringBuilder.append(param.toString().replace("'", "\\'"));
stringBuilder.append("'");
}
if(i < params.length - 1){
stringBuilder.append(",");
}
}
stringBuilder.append(")}catch(error){Android.onError(error.message);}");
webView.loadUrl(stringBuilder.toString());
}
You need to add this too:
private class WebViewInterface{
#JavascriptInterface
public void onError(String error){
throw new Error(error);
}
}
And add this interface to your webview:
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Yes you have the syntax error. If you want to get your Javascript errors and printing statements in your logcat you must implement the onConsoleMessage(ConsoleMessage cm) method in your WebChromeClient. It gives the complete stack traces like Web console(Inspect element). Here is the method.
public boolean onConsoleMessage(ConsoleMessage cm)
{
Log.d("Message", cm.message() + " -- From line "
+ cm.lineNumber() + " of "
+ cm.sourceId() );
return true;
}
After implementation you will get your Javascript errors and print statements (console.log) on your logcat.
Modification of #Ilya_Gazman answer
private void callJavaScript(WebView view, String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
String separator = "";
for (Object param : params) {
stringBuilder.append(separator);
separator = ",";
if(param instanceof String){
stringBuilder.append("'");
}
stringBuilder.append(param.toString().replace("'", "\\'"));
if(param instanceof String){
stringBuilder.append("'");
}
}
stringBuilder.append(")}catch(error){console.error(error.message);}");
final String call = stringBuilder.toString();
Log.i(TAG, "callJavaScript: call="+call);
view.loadUrl(call);
}
will correctly create JS calls e.g.
callJavaScript(mBrowser, "alert", "abc", "def");
//javascript:try{alert('abc','def')}catch(error){console.error(error.message);}
callJavaScript(mBrowser, "alert", 1, true, "abc");
//javascript:try{alert(1,true,'abc')}catch(error){console.error(error.message);}
Note that objects will not be passed correctly - but you can serialize them before passing as an argument.
Also I've changed where the error goes, I've diverted it to the console log which can be listened by:
webView.setWebChromeClient(new CustomWebChromeClient());
and client
class CustomWebChromeClient extends WebChromeClient {
private static final String TAG = "CustomWebChromeClient";
#Override
public boolean onConsoleMessage(ConsoleMessage cm) {
Log.d(TAG, String.format("%s # %d: %s", cm.message(),
cm.lineNumber(), cm.sourceId()));
return true;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.bluapp.androidview.R;
public class WebViewActivity3 extends AppCompatActivity {
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view3);
webView = (WebView) findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/webview1.html");
webView.setWebViewClient(new WebViewClient(){
public void onPageFinished(WebView view, String weburl){
webView.loadUrl("javascript:testEcho('Javascript function in webview')");
}
});
}
}
assets file
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>WebView1</title>
<meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
</head>
<body style="background-color:#212121">
<script type="text/javascript">
function testEcho(p1){
document.write(p1);
}
</script>
</body>
</html>
Here is an example to load js script from the asset on WebView.
Put script to a file will help reading easier
I load the script in onPageFinished because I need to access some DOM element inside the script (to able to access it should be loaded or it will be null). Depend on the purpose of the script, we may load it earlier
assets/myjsfile.js
document.getElementById("abc").innerText = "def"
document.getElementById("abc").onclick = function() {
document.getElementById("abc").innerText = "abc"
}
WebViewActivity
webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
val script = readTextFromAsset("myjsfile.js")
view.loadUrl("javascript: $script")
}
}
fun readTextFromAsset(context: Context, fileName: String): String {
return context.assets.open(fileName).bufferedReader().use { it.readText()
}