Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
It was not (so) obvious
6 mars 2014

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

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!

Publicité
Publicité
Commentaires
It was not (so) obvious
Publicité
Archives
Publicité