Apps based on webviews
In order to save time and money, you might be tempted to build a website, then wrap it inside a native app. The idea is to get the best of the 2 worlds: pages running on any platform (with HTML 5 you can do almost anything you'd do natively) and native features that you can't get even with HTML 5 such as push notifications.
Easier to tell than to do though. You might face different problems with webviews:
Cache manifest
A cool feature that should allow your users to use the app even offline. Not so obvious to make it work though:
On Windows Phone
- No cache manifest on Windows Phone 7. That also means that you can't make an app for both Windows Phone 7 and 8 and use the cache manifest because the WebBrowser component on WP 8 will act exactly like the WP7 one.
- You can't use GET parameters in the cache manifest.
- As a bonus, there's a weird bug when using jQuery.ajaxSetup({isLocal:true}); cache manifest and Ajax on WP8. See http://social.msdn.microsoft.com/Forums/wpapps/en-US/5c101164-ab98-4b32-a01f-9b5da3bd18fa/get-parameters-in-cache-manifest-on-windows-phone-8?forum=wpdevelop for more details
On Android
- You'll have to configure the webview so that it can cache data. For this you can use
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
if (activeNetwork != null && activeNetwork.isConnected()) {
webSettings.setAppCacheMaxSize(1024*1024*32);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
}
else{
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
}
String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
webSettings.setAppCachePath(appCachePath);
webSettings.setAllowFileAccess(true);
webSettings.setAppCacheEnabled(true);
- Note that setAppCacheMaxSize has been deprecated. The javadoc tells This method was deprecated in API level 18. In future quota will be managed automatically. Apparently future is not now yet and this seems to be still required.
- You might also want to override a WebChromeClient method like so
private class CustomChromeClient extends WebChromeClient{
@Override
public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
WebStorage.QuotaUpdater quotaUpdater)
{
quotaUpdater.updateQuota(spaceNeeded * 2);
}
}
And assign this ChormeClient to your webview: webview.setWebChromeClient(new CustomChromeClient());
Bridge between Javascript and the native app
You might want your native code to talk with the Javascript of your website. Unfortunately, every platform addresses this problem in a different way
On Android
- Javascript to Java
You'll need to create a new Java class (let's say AndroidBridge) containing the methods you want to be able to call from Java and use the @JavascriptInterface annotation on them. For instance:
@JavascriptInterface
public void clearEvent(){
activity.saveCurrentEvent(null);
}
Then add this class as a javascript interface to your webview
webview.addJavascriptInterface(new AndroidBridge(this), "AndroidBridge");
You can then call these methods from Javascript like so:
if(window.AndroidBridge){
window.AndroidBridge.clearEvent();
}
You can of course give parameters to the method.
- Java to Javascript
Use WebView.loadUrl with javascript as the parameter. For instance webview.loadUrl("javascript:(function(){goback();})()");
On Windows Phone
- Javascript to C#
On the C#, you have to use the Browser.ScriptNotify event handler. Assign a method to it and there you go.
Browser.ScriptNotify += new EventHandler(Browser_ScriptNotify);
//Javascript interface
void Browser_ScriptNotify(object sender, NotifyEventArgs e)
{
String[] parameters = e.Value.Split(new Char[] {':'});
...
}
On the Javascript side, use window.external.notify (be careful though as Firefox also uses window.external and you don't want code for WP to execute on Firefox):
else if(window.external && 'notify' in window.external){ //Firefox uses window.external for other purposes
window.external.notify("subscribePushNotifications:" + event);
}
Note that you can give only one parameter, so if you have to transmit several variables, concatenate them on the Javascript side using a separator and split it on the C# side
- C# to Javascript
Simply use WebBrowser.InvokeScript, for instance Browser.InvokeScript("goback");
On iOS
There's no native support for Javascript/Objective C communication but there are workarounds. For my project I used a bridge created by Marcus Westin that you can find here:
https://github.com/marcuswestin/WebViewJavascriptBridge
Follow the instructions there and it should work just fine.
Bonus: Setting up the webview in Android
It's not that obvious to make your android webview act as if it was a browser. Here's the piece of code I used for my own project. You might want to adapt it to your own needs.
webview = (WebView) findViewById(R.id.webview);
webview.setWebViewClient(new WebViewClient());
webview.setWebChromeClient(new WebChromeClient());
webview = (WebView) findViewById(R.id.webview);
final ConnectivityManager conMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
webSettings.setDefaultZoom(ZoomDensity.MEDIUM);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setUseWideViewPort(true);
webSettings.setDomStorageEnabled(true);
if (activeNetwork != null && activeNetwork.isConnected()) {
webSettings.setAppCacheMaxSize(1024*1024*32);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
}
else{
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
}
String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
webSettings.setAppCachePath(appCachePath);
webSettings.setAllowFileAccess(true);
webSettings.setAppCacheEnabled(true);
webview.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
Bonus: history.back() on iOS7
It simply doesn't work on iOS7. It also means that data-rel="back" from jQueryMobile won't work on these devices. Yay!