Search This Blog

Saturday, January 30, 2010

Android WebView

Android WebView enables us to display web pages and interact with web content, there are several reasons that you might want to use WebView in your applications, but i think one the most interesting features of WebView is JavaScript integration capability. in this article I'm gonna show you a simple example of how easy it is to call javascript functions from your java code and vice versa.
first of all let's see how our example will be looking like :


it's actually just a WebView which is displaying a local Html page, this html page contains a java script slideshow component, I've actually used the very same code that i found over here. the original javascript component has two button which can be used to slide into the next or previous page, but what I wanted was to be able to slide through pages using Swiping Gestures, Like...Swipe Left...go to next page, Swipe Right ...previous page.


There are two javaScript functions , moveBackward() which replaces the current Item with previous one using a left to right sliding effect and moveForward() which does the exact opposite thing. all we need to do is to detect user Gestures, if it is a right to left gesture (swipe left) we will call moveForward()function and if it is a swipe right gesture we will call moveBackward() function, in this example I've used SimpleGestureFilter class (see my last post) and here is our onSwipe method implementation:



@Override
public void onSwipe(int direction) {


switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");
break;
case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");
break;
case SimpleGestureFilter.SWIPE_DOWN :
case SimpleGestureFilter.SWIPE_UP :

}
}


It's much easier than you though it would be, isn't it? we just need to pass the name of our javascript function (with 'javascript:' prefix) to loadUrl() method of the WebView instance.
as you can see in the pictures above, we have 3 buttons in our html form 'Add', 'Remove' and 'Report', we also have a Vector of Strings in our Activity; when 'Add' button is pressed the name of the current item will be sent to our activity and got stored in the Vector, when 'Remove' button is pressed the name of current item will be removed from Vector and in both cases a message will be shown afterwords that the operation has just been done :


if user presses the "Report" button a javascript popup box will be shown which says how many items are currently stored in our Vector:


here is the html and javascript code that is being used to render those 3 buttons:


.
.
.
var items = ["British Columbia", "Ontario", "Yukon","New Brunswick"];

function report(){

var contentStr = window.interface.getReportContent();

var contentDiv = document.getElementById("showBoxContent");
contentDiv.innerHTML = contentStr;
showBox();
}
.
.
.
<input type="button" onclick="window.interface.add(items[configParams.selected])" value="Add"/>
<input type="button" onclick="window.interface.remove(items[configParams.selected])" value="Remove"/>
<input type="button" onclick="report()" value="Report"/>
.
.
.


and here is our Activity source code :



public class WebViewSample extends Activity implements SimpleGestureListener{

private Handler handler = new Handler();
private WebView webView;
private SimpleGestureFilter filter;
private Vector<String> provinces = new Vector();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.webview_layout);
webView = (WebView) findViewById(R.id.webview);

WebSettings webSettings = webView.getSettings();
webSettings.setSaveFormData(false);
webSettings.setJavaScriptEnabled(true);

this.filter = new SimpleGestureFilter(this,this);
this.filter.setMode(SimpleGestureFilter.MODE_TRANSPARENT);

webView.addJavascriptInterface(new MyJavaScriptInterface(), "interface");

webView.loadUrl("file:///android_asset/test.html");
}

@Override
public boolean dispatchTouchEvent(MotionEvent me){
this.filter.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}

@Override
public void onSwipe(int direction) {

switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");
break;
case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");
break;
case SimpleGestureFilter.SWIPE_DOWN :
case SimpleGestureFilter.SWIPE_UP :

}
}


@Override
public void onDoubleTap() {
}


private void addOrRemove(String name,boolean add){

String msg = null;
boolean alreadyAdded = this.provinces.contains(name);

if(add){
if(!alreadyAdded){
this.provinces.add(name);
msg = name+" Has just been added to the List.";
}
else
msg = name+" Has already been added to the list.";
}
else{
if(alreadyAdded){
this.provinces.remove(name);
msg = name+" Has just been removed from the List.";
}
else
msg = name+" has never been added to the list!";
}

Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}


final class MyJavaScriptInterface {

MyJavaScriptInterface() {
}

public void add(final String name) {
handler.post(new Runnable() {
public void run() {
addOrRemove(name, true);
}
});
}

public void remove(final String name) {
handler.post(new Runnable() {
public void run() {
addOrRemove(name, false);
}
});
}

public String getReportContent() {
if(provinces.size() == 0)
return "<i> The list is empty. </i>";

String content = "<i>You have added these provinces into the list :<i><br/><br/>";
for(int i=0;i<provinces.size();i++)
content += (i+1)+"- "+provinces.get(i)+"<br/>";

return content;
}

}

}


as you can see all we have is a simple WebView instance which displays the content of 'test.html' file that is located in assets directory of our project.

we've called addJavascriptInterface() method of our WebView instance in order to be able to call java methods in javascript code this method has two parameters, the first one is an Object methods of which we want to call in our javascript code and the second parameter is an String which will be used as the name of that java object in javascript context. once addJavascriptInterface() method is called the specified java object will be attached to the Html document's window object and can be referred by its name.
Remember that when methods of the passed java object is called in javascript context, it's not gonna invoked in the Main(UI) thread, and if you need to get it called in the UI thread you will need to use a Handler and post a new runnable to run your code, just like what I've done for add() and remove() methods in this example (though it is not necessary in this example).
another important thing to remember is the fact that you cannot pass or return any complex object to or from javascript context, that is all you are allowed to use are primitive and String data types.

13 comments:

Anonymous said...

how did you solve the background thread problem?

Amir said...

what background thread are you exactly referring to?

MrU said...

Very nice tut!
I was going crazy trying to make javascript work on a webView wich was loading local files.
My problem was...¡I was placing the files on res/raw. Since i moved them to assets, everything began to go smooth as it should.
I can't wait to get home and try what i've just seen here!!
Thanks Amir!

Peter said...

I can't get this to work. Am I missing something? I've tried creating the classes and placing them in my codebase but it just gives me errors.

Amir said...

Peter, What is the error message you're getting?

Peter said...

It says "SimpleGestureListener cannot be resolved to a type" at the beginning of the code @:

public class WebViewSample extends Activity implements SimpleGestureListener{

Amir said...

have you added SimpleGestureFilter class to your project?
SimpleGestureListener is actually an inner interface which has been defined inside SimpleGestureFilter, you can find this class here as I have already mentioned in this article. but if you have got this class already, the only thing i can think of is package conflict, check you import statements and make sure the right class has been imported.

QD said...

A couple of questions:

1. in javascript how to code moveBackward() and moveforward()

2. how did you make your three buttons (add, remove and report) along with the paginated buttons (1,2,...10) in the same line.

3. If I have 10 pages, and 10 buttons, how to make just first and last button and prev and next button? You mentioned that more paginated buttons could be added because they would be hidden. how?

Thank you very much

Anonymous said...

I did my own custom implementation of a WebBrowser for Android, using the WebView control, and with a Javascript I have access to the HTML code of the page. Here is a sample:
http://www.pocketmagic.net/?p=1776

Anonymous said...

I am attempting to "flip" through local URLs. My layout is:

TableLayout
WebView
TableLayout

I have controls in my outer TableLayouts and my local HTML scaled into the Webview. Works great until I click my "Next" control and call WebView.loadUrl again. This causes the next HTML to take over all of the screen real estate.

Just wondering about using your technique to just replace the innerHTML of a DIV tag instead of calling loadUrl? It appears the Android doesn't like the initial layout to be messed with.

Thanks

ayrina said...

HI,
even i m facing an issue with android webview i had to place a webview and a button on it...ot below the webview..can share your webview layout(xml)
as u are able to open ur webview page and button separately..
thank
Ayrina

Anonymous said...

Man you rock!! just awesome.

smitha prakash said...

Hi Amir,
First of all Thank you for Sharing this.I was struggling for this from a week. It did really help me.And I Must tel you about the coding part. Its perfectly done.. Thumbs up man..:)