Progressive Web apps recipes for GWT
Progressive or not progressive…
If you have been designing or developing web applications for a while, you would have probably came across the term Progressive Web application a tons of times, and will probably do so for the coming years. You probably wondered what exactly is the definition of PWAs, how we identify one, and how we build one. According to the dictionary, the term progressive refers to something that improves or gets better, but how would that relate to a web application ? We really don’t know. PWAs seems like a buzz word invented by Google to get people’s interest, and does not really relates to what the PWAs really are. PWAs have been defined by Alex Russel as “Web sites that took the right vitamins”. For the sake of simplicity let’s start by saying that PWAs are web applications that are optimized to fit in their environment: they can play the role of mobile native apps when on mobile or tablet, and the role of regular web apps when on PC.
The rationale behind PWAs:
PWAs are an alternative to having a regular web application plus a bundled application for different mobile platforms. Maintaining and upgrading all of these can be costly especially if the application is changing frequently. With PWAs, there is only one application that works for all platforms which is accessible from a link in a browser. PWAs are meant to be designed using a Mobile first approach. They can be installed, but they also work well as regular websites. Google have created a dedicated web site to PWAs and presents different cases of companies who gained from converting their applications/website to a PWA.
Charachteristics of PWAs:
In one of his talks, Rob Dodson, a developer advocate at Google, highlighted the different characteristics of a Web app:
– Responsive: adapts to devices
– Load fast: optimized to paint or render fast
– Work Offline: use service workers for caching content to allow using the app offline or with slow network connectivity
– Installable: application can be installed in the home screen (like a native app)
– Engaging: keep the user informed using push notifications
Now that we know what a progressive web app looks like, we can start looking into different tools that can help us make our GWT app progressive.
GWT Recipes for PWAs:
- #1 Reponsive:
To make your GWT application responsive, there are several options for GWT users. If you have design skills, you can make your application responsive using custom code and CSS. Otherwise, you can rely on other frameworks. Bootstrap for GWT (https://github.com/gwtbootstrap3/gwtbootstrap3) is the first thing that comes to mind. It provides all the components of the famous Twitter framework. Another alternative is GWTMaterialDesign (https://github.com/GwtMaterialDesign/gwt-material). It provides repsonsive material design ready to use elements for your application. Finally, gwt-polymer-element, which is the Polymer wrapper for GWT, provides also ready to use responsive web components, and can come handy in designing building a reponsive application. We have provided a beginners guide to Polymer in one of our previous posts.
- #2 Load Fast:
To reduce the time to the first paint, there are a number of things that can be done. First of all, code splitting can be used to reduce the size of the gwt module file. It basically splits the module into fragments allowing the GWT module to download only the needed ones on startup. Second, the app shell method, as specified by PWAs guidelines, can be applied to a GWT app. This can be done by taking out static elements and data from the application Java code and putting them directly into the .html entry point. For example:
A common practice that GWT users do is to have the body of the .html empty and to add their views programmatically from the application:
<body> </body>
//.... AppMainView view = AppMainView(); RootPanel.get().add(view);
While there is nothing wrong with this practice, it can slow down the application loading time because the .js module file will have more instructions, and thus it will take more time to execute. As a remedy, we can try to identify all the static elements in our views and put them into the .html, and then we can load individual views from our entry point:
<div id="appShell"><img src="logo.png" alt="" /> <div id="menu"></div> <div id="mainContent"></div>
//... MenuView menu = new MenuMeview(); ContentView content = new ContentView(); RootPanel.get("menu").add(menu); RootPanel.get("mainContent").add(content);
This is off course a simplified example for illustration purposes. We have seen so far how code splitting and the app shell can reduce the time to render the application. There is also the async script attribute of HTML5, which is not really specific to GWT. For example:
<!-- Inside HEAD --!> <script src="polymerstarter/polymerstarter.nocache.js" async="" type="text/javascript">
This would instruct the browser not to block the parsing, and to load our app script as soon as it is available.
Another option would be to put the application script inside the body.
- #3 Work Offline:
This can be done mainly using service workers. There are no official GWT libraries for interacting with service workers. Even gwt-polymer-elements does not wrap Platinum Elements, which are the Polymer elements meant to interact with the browser’s service workers. GWT users will have to write some Javascript manually to implement the caching mechanism for the application’s assets. JSNI or Jsinterop can be used to interact with the browser and call for service workers services. The service worker script that defines caching events needs to be on a separate script so, for now, it is kind of complicated to mix in both the service worker code and GWT app module code in the same .js file. The only task that can be done from GWT is registering the service worker. We will demonstrate that later in the next section. Please also note that service workers are not available on all browsers, you can find more details on that in Mozilla’s API doc page.
For more details about how to cache application data and assets using service workers, Google provides some useful guidelines.
- #4 Intstallable:
This receipe is also not specific to GWT. To make a web application installable, you need to add a json file called app manifest and link it to the .html entry point:
<link rel="manifest" href="manifest.json">
For guidelines on how to write the manifest file you can refer to W3C’s guidelines: https://www.w3.org/TR/appmanifest/. You can also use this online tool: http://brucelawson.github.io/manifest/ which generates your manifest for you, but your application need to be already online. You can either use a banner to ask the user to install the application or let him do it manually from the browser’s options.
- #5 Engaging:
Once again there is no official push notification library for GWT. This may be a call to the GWT community to fill in this gap. Until then, GWT users can use either JSNI or Jsinterop to interact with the browser and subscribe to push notifications.
Demo application
To illustrate the characteristics above, we built a map application using gwt-polymer-elements and gwty-leaflet. The application displays the favorite maps of the user.
source: https://github.com/gwidgets/gwt-pwa-demo
live: https://gwt-pwa-demo.herokuapp.com/pwademo.html/
using Polymer our application is responsive by default, so this step is done.
To make the application load fast we first of all took off all the static html and we put into the .html entry point file: https://github.com/gwidgets/gwt-pwa-demo/blob/master/src/main/webapp/pwademo.html
We used Polymer elemental to interact with the dom elements. For example:
PaperMenuLEement paperMenu = (PaperMenuElement) Polymer.getDocument().getElementById("paperMenu"); paperMenu.select("paris");
We also made our app script load asynchronously:
<script type="text/javascript" language="javascript" src="pwademo/pwademo.nocache.js" async></script>
and we introduced some code splitting because we only have one map per section, so we only need to load the map on the section displayed when the page is loaded.
loadStartupMap(); //Maps are not loaded on start up, but only when iron selector selects a new map ironPages.addEventListener("iron-select", e -> { if(ironPages.getSelected().equals("london") && !londonMapInitialized){ //Some code splitting to reduce initial module size GWT.runAsync(new RunAsyncCallback(){ @Override public void onFailure(Throwable reason) { Document.get().getElementById("londonMap").setInnerHTML("Could not load this map, please try again later"); } @Override public void onSuccess() { Maps.initializeLondonMap(); }}); londonMapInitialized = true; } });
We have also added an application manifest to allow the application to be installed manually
{ "name": "Favorite Maps PWA", "short_name": "Favorite Maps PWA", "icons": [{ "src": "image/mapicon.png", "sizes": "144x144", "type": "image/png" }], "start_url": "/pwademo.html", "display": "standalone", "background_color": "#3E4EB8", "theme_color": "#2E3AA1" }
Finally, we have added JsInterop classes to register the service worker.
if (Navigator.serviceWorker != null) { Navigator.serviceWorker.register("sw.js") .then(new Function<JavaScriptObject, JavaScriptObject>() { @Override public JavaScriptObject call(JavaScriptObject arg) { GWT.log("registred service worker successfully"); return null; } }); } else { GWT.log("service worker unavailable in this browser"); }
and we created a service worker script called sw.js and added it to the application’s resources.
var cacheName = 'GWT-PWA'; var filesToCache = [ '/gwt-pwa/pwademo.html', '/gwt-pwa/pwademo.css', '/gwt-pwa/styles/app-theme.html', '/gwt-pwa/styles/shared-styles.html', '/gwt-pwa/leaflet/leaflet.js', '/gwt-pwa/leaflet/leaflet.css', '/gwt-pwa/image/mapicon.png', '/gwt-pwa/pwademo/pwademo.nocache.js']; self.addEventListener('install', function(e) { console.log('[ServiceWorker] Install'); e.waitUntil( caches.open(cacheName).then(function(cache) { console.log('[ServiceWorker] Caching app shell'); return cache.addAll(filesToCache); }) ); }); self.addEventListener('activate', function(e) { console.log('[ServiceWorker] Activate'); e.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { console.log('[ServiceWorker] Removing old cache', key); if (key !== cacheName) { return caches.delete(key); } })); }) ); }); self.addEventListener('fetch', function(e) { console.log('[ServiceWorker] Fetch', e.request.url); e.respondWith( caches.match(e.request).then(function(response) { return response || fetch(e.request); }) ); });
the script installs and activates the service worker. It also allows the service worker to subscribe to the fetch event which triggered on each request for a resource. Based on its current state, the service worker decides then whether to use the local cache or to fetch the resource from the network.
After loading the application, we can find our assets in the cache storage in Google chrome:
http://www.g-widgets.com/wp-content/uploads/2016/08/cacheChrome.png
if we disable the network on Google Chrome and try to run the application, we get something like (Map is not rendering because it is not cached):
The application is serving even without network. If we take a look a the network requests in Chrome dev tools, we notice that the app resources are being served from the service worker:
As this is a demo application we did not add any push notification because it requires the setup of a push server.
We have installed the application to home screen from an Android phone, and we got something like:
Conclusion
PWAs are still something new in the web developement world. Some predict that they will take over native apps in the coming years. We know that GWT developers have been using Phonegap to convert their web application to a mobile native app, and maybe with the PWAs they will not have to do so anymore. We have seen in this tutorial how GWT can be used to build a PWA, using libraries such as Polymer. There so far are no GWT libraries to interact with the browser service workers, so this gap needs to be filled by the GWT community.
Interesting links
Addy Osmani beginner’s guide: https://addyosmani.com/blog/getting-started-with-progressive-web-apps/
2016 Spring IO talk about PWAs and Spring Boot: https://www.youtube.com/watch?v=zAZQeQ0CRpQ
A summary infographic of PWAs use cases from https://skilled.co/, a web development online agency:
Presented by Skilled.co
Reference: | Progressive Web apps recipes for GWT from our JCG partner Zakaria Amine at the G-Widgets blog. |