Create a lightweight version of PhoneGap in Android

Takeaway: in this PhoneGap tutorial, mobile developer Kyle Miller presents some tricks about how to use WebViews in Android and more.

One of the most interesting features of the PhoneGap cross-platform framework for developing mobile applications is the javascript-to-native communication that occurs. The ability to send commands for execution in either direction is quite impressive, and you may be wondering how they do it. I’ll explain how PhoneGap achieves this communication, and show how to create a lightweight version of PhoneGap in Android. There are a couple of ways to intercept calls from the javascript, and I’ll go over all of the approaches.

Also read: PhoneGap: What app developers need to know, PhoneGap plugin vs. a native solution in Android, Set-up an environment for Android, Eclipse and PhoneGap

A couple of the possible solutions reside in the WebChromeClient class; any of the methods that allow you to capture a message from the javascript are great candidates. The onConsoleMessage, onJsAlert, onJsConfirm, and onJsPrompt methods should work in theory, although I have not tried them all. The only thing that would differ amongst these methods is how you’d trigger them from the javascript. if you wanted to use onConsoleMessage, then from the javascript you could call console.log() to pass a message through to the native side. And if using onJsAlert, all you have to do is call alert() from the javascript to intercept the message.

Another class you could use to achieve this communication is the WebViewClient class. The best choice in this class is the shouldOverrideUrlLoading method. This method will get called whenever the WebView attempts to load a new page, so you could pretend to load a new page via javascript and pass through the data you want to send in the URL.

While all of these approaches will work, I recommend using onJsAlert or shouldOverrideUrlLoading. The only reason I’d advise against using onJsAlert is that if you were trying to create a cross-platform javascript library you could also extend on iOS. I haven’t been able to find an onJsAlert equivalent for iOS, but I did stumble across shouldStartLoadWithRequest, which resides in the WebView delegate. That said, if you use shouldOverrideUrlLoading and shouldStartLoadWithRequest, you should be able to take the same javascript approach for Android and iOS. For the sake of this post, we’ll assume you’re using shouldOverrideUrlLoading.

JSON is going to be your best friend when you’re trying to communicate back and forth between the javascript and Java. if you’re not very familiar with JSON, now is the time to get a little more comfortable with it, although the JSONArray and JSONObject classes in Java, combined with a free json jquery library will make things pretty cut-and-dry.

So, from the javascript you can invoke the shouldOverrideUrlLoading method by calling:

document.location = <some_url>;

You will want to be able to identify which calls are coming from your javascript vs. which calls may be coming from other valid HTML links. To do this, pick a protocol for your app and pre-pend all message URLs with your protocol such as (assuming your protocol is ‘myapp’):

document.location = “myapp://testing”;

Next, you need to decide what kind of data structure you want to send to the native side so all requests can be uniform. I suggest sending an object such as:

var obj = { type:'<type>’, action:'<action>’, data:<data> }

where type can be something like “error” or “action,” action can be an action you want to execute, and data can be any additional data you may need to pass.

In order to convert this object to a JSON string that you can append to the end of your custom url, I find the following library very useful. It’s incredibly easy to use — all you do is pass your object to the $.toJSON function, and it will serialize the object into a JSON-formatted string:

document.location = “myapp://” + $.toJSON( <object> );

You will need to create a custom class that extends WebViewClient and overrides shouldOverrideUrlLoading inside your Android app. Below is an example:

public class MyWebViewClient extends WebViewClient {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {if( url.contains(“myapp://”) ) {url = url.replace(“myapp://”, “”);JSONObject obj = new JSONObject(url);// query the JSONObject to decide what to do nextreturn true;}else {return false; // let URL go through to browser}}}

Next, you need to add a WebView to your layout XML file (such as main.xml):

<LinearLayout xmlns:android=”schemas.android.com/apk/res/android”android:orientation=”vertical”android:layout_width=”fill_parent”android:layout_height=”fill_parent” ><WebView android:id=”@+id/MyWebView”android:layout_width=”fill_parent”android:layout_height=”fill_parent”android:scrollbars=”none”></WebView></LinearLayout>

Then drop your HTML, CSS, and JavaScript resources into the “assets” folder of your project.

Finally, set your custom WebViewClient to the WebView in your layout and load the local html asset in the WebView. Below is a sample onCreate method of an Activity that will do this:

@OverrideonCreate( Bundle savedInstanceState ) {super.onCreate(savedInstanceState);setContenteView(R.layout.main);WebView myWebView = ((WebView)findViewById(R.id.MyWebView));myWebView.setWebViewClient( new MyWebViewClient() );myWebView.loadUrl( “file:///android_asset/index.html” );}

If you wish to execute a method in the javascript from the native Java side, you can use the loadUrl method on the WebView and pass “javascript:<code_to_execute>.” This can be extremely useful for providing callback methods to the javascript. You can imagine that if you were to call toString() on a JSONObject or JSONArray, you could very easily pass entire data sets back to a function in the javascript.

Now that the curtains have been pulled back, does it still seem as magical? I hope you at least learned some tricks about WebViews in Android.