RepreWho – take 2 Final Project
The greatest app to ever storm the CS491 class is back in a brand new shiny interface! Ok, so probably not the best app, but definitely the most fun one I wrote. Of all the apps I wrote it’s pretty much the only one I’d consider actually publishing. But then I started thinking – I want to publish this on iOS too, but making an iOS native app separately makes me sad face. So, I decided to adapt RepreWho to an HTML5, jQuery Mobile app. I went to a 45 minute session once about jQuery mobile – it seemed pretty straightforward – I can totally do this. For the most part, that was actually correct, with one major, hugo caveat that I’ll get to later.
But then I thought, wait, the coolest feature of RepreWho was making phone calls…HTML5 can’t do that. So, my solution was to make the majority of the application in HTML5, then wrap that in a thin native app. Sounds cool, right? Yeah, wish I’d been the first one to think of that. These guys, and probably many others, beat me to it.
Now, I didn’t get the iOS application finished….some technical issues I’m still trying to work out. If I finish it in the next few hours I’ll post some code / screen shots.
Much of this post mordem is going to be a comparison between the HTML5 version and the original Android version. Ok, too many words, not enough pictures:
Android Screen Shots: HTML5/jQuery Screen Shots:
Ok, so the first thing you probably noticed is that image aren’t loading in the jQuery Mobile version. Didn’t have time to get the Google Image Search api in. In comparing the two, the jQuery mobile one looks much better to me. And the cool thing is that the entire layout of the jQuery mobile version has one .html file, one .css file, and one .js file. That’s it. About 250 lines of code in total. The Android version had 12 java classes and 8 layout.xml files.
Here’s the Android that I needed for the simple native app that wrapped the jQM app:
public class MainActivity extends Activity { private WebView webview; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); webview = (WebView)findViewById(R.id.webView); webview.getSettings().setJavaScriptEnabled(true); webview.loadUrl("file:///android_asset/main.html"); webview.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(url.startsWith("app-phone://")){ String phone = url.substring(13); Intent callIntent = new Intent(Intent.ACTION_DIAL); callIntent.setData(Uri.parse("tel:" + phone)); startActivity(callIntent); return true; }else if(url.startsWith("app-link://")){ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.substring(11)))); return true; }else if(url.startsWith("app-email://")){ final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); emailIntent.setType("plain/text"); emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{url.substring(13)}); emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject"); emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Text"); startActivity(Intent.createChooser(emailIntent, "Send mail...")); return true; }else{ return super.shouldOverrideUrlLoading(view, url); } } }); } @Override public void onBackPressed() { if(webview.canGoBack()){ webview.goBack(); }else{ super.onBackPressed(); } } }
Not much code at all. All it’s really doing is pulling in the WebView from the layout, turning on javascript, and setting the WebView’s WebViewClient. The class give a hook into the WebView trying to load URLs. This is how I link the call, email, and external link functionality back into the Android ecosystem. When I load a Representative in the HTML, I simply add some nonsense “protocols” to the start of the links that I want Android to intercept. I then pull out the actual data, and start the respective Intent.
WebViews provide another very nice features. By default, WebViews thread things so that the UI doesn’t hang. Granted, you can still kill get the UI thread to hang, but as long as you avoid infinite loops you’ll be fine.
So this is all fine and dandy, everything’s easier in JavaScript. Well it certainly looks that way…the problem comes in when trying load data from a 3rd party. People are very concerned about security when it comes to JavaScript. As a result, the standard way of hitting websites (ajax calls) fail. Because the government api is on a different host, ajax calls fail. Well that’s no good. I tried every hackish thing I could think of – pulling in text instead of json, json-p, manipulating the src of an iframe then probing it with jQuery….nothing worked….until I put the html files in Android. For some reason scripting attacks are apparently not an issue when running in Android…which makes no sense to me, but hey. So I just hardcoded a “fake” response and continued developing in my browser.
My only other complaint about the HTML5 /jQuery approach is that it is considerably slower than native apps. But that’s the trade off for portability I guess….unless I have no idea what I’m doing with my jQuery.
The approach for iOS is essentially the same as the Android approach. UIWebView with a delegate that decides how to handle loading of resources.