Monday, December 28, 2015

Service Worker - Give native app experience to web app on mobile

Context:

In today's mobile app development, there are lot of things web apps on mobile can't do compared to native apps. But the gap is filling fast. Offline access is one such example. Facebook native app for example, when disconnected shows recent 15+ posts. You may continue to use the app. When connected to Internet it will sync data. Imagine similar functionality to a Web App. It's especially useful on  mobile devices - phones and tablets.

Service Worker helps cache application (JavaScript, HTML, CSS) and data. It's a specification created by Google Chrome and Mozilla.

Following is a sample I tried out with SW-PreCache and SW-Toolbox repos. I'm documenting steps to create the service worker in the sample app below, The sample app shows dinosaur data from a Web URL (thanks to Firebase - it's one of the sample data sets provided by Firebase).

Sample App

Here is the complete code repo for sample app.

The sample app is coded using AngularJS, Bootstrap CSS framework for responsive design on mobile screen. Controller accesses dinosaur data from Web URL and assigns to scope. If there is a problem accessing data it will set flag so that UI can show error message.

$http.get("https://dinosaur-facts.firebaseio.com/dinosaurs.json")
.success(function(results){
$scope.dinosaurs = results;
})

.error(function(error){
  // Set flag - show error message when there is a problem retrieving data.
$scope.showAlert = true;
$scope.errorMessage = "Ooops, Jurassic park is unavailable! Are you connected to Internet?";

});

View is HTML in index.html - ng-repeat to show data.

<div class="container" ng-controller="firstController">
<table class="table table-striped">
<tr ng-repeat="(dinosaur,prop) in dinosaurs">
<td><strong>{{dinosaur}}</strong></td>
<td>Appeared {{prop.appeared}} years ago</td>
<td>{{prop.height}} meters long</td>
<td>{{prop.length}} meters wide</td>
<td>{{prop.weight}} pounds</td>
</tr>
</table>
</div>


Or show error message when no there is no connectivity,
<div ">
<div ng-show="showAlert" class="alert alert-danger alert-dismissible " role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{{errorMessage}}
</div>

Browser error vs graceful error: 

On a browser go to a link when offline, you would see browser error page. How about native like graceful error message? Create a shortcut to home screen like a native app and as you tap it graceful error message is shown (if there is no network connectivity). You could provide more details of the error as well. It's better user experience compared to browser error page. Following is an example




For this, we could use Service Worker to cache application skeleton. SW-PreCache makes this process simple.
  1. Install using bower install sw-precache
  2. Modify grunt file in demo app, Update staticFileGlobs property to new files in the sample. Modify handleFetch property to true so that it starts serving cached content. Here is the link to new file.
  3. Run grunt swPrecache. It now generates service-worker.js. It need to be registered with Service Worker on the browser. A service-worker-registration.js file is reusable. It takes care of checking service worker feature availability on the browser, checking if there is a newer version of service-worker.js so that it can be re-installed etc.
  4. Include this file in Index.html so that registration happens.
You are done. As you run the app, all specified files in grunt task are cached. Even if network is disconnected, cache is used. 

Note: I'm using grunt to generate pre-cache for the sample. Gulp is supported and there are multiple examples using gulp as well.

Cache Data:

sw-toolbox helps cache data easily. 
  1. Install sw-toolbox using bower. bower install sw-toolbox
  2. Import toolbox script using importScripts('node_modules/sw-toolbox/sw-toolbox.js');
  3. Provide following configuration for caching data from given XHR calls. It caches all calls to dinosaur-facts.firebaseio.com. 
toolbox.router.get('/(.*)', toolbox.networkFirst, { 
 // Use a dedicated cache for the responses, separate from the default cache. 
 cache: { 
   name: 'sample-app', // Store up to 10 entries in that cache. 
   maxEntries: 10, // Expire any entries that are older than 30 seconds. 
   maxAgeSeconds: 30 
 }, 
 origin: 'dinosaur-facts.firebaseio.com' 
});
This Service Worker once registered with the browser will use cached data when disconnected.

It caches any URL pattern. Handler toolbox.networkFirst is used so that first preference is given to network. Only if network connection is unavailable, cached data is used. We could use cacheFirst where data is immediately loaded from cache and then updated once obtained over network. More options are fastest - make both calls network and cache, whichever comes first will be used. networkOnly - when you never want to use cached data for a route. cacheOnly - when you are sure network call won't be made for that route.

Here is a quick demo of Service Worker Sample App


Link to the demo page.

In conclusion I believe Service Worker is going to be revolutionary for mobile web app development. Recent Chrome Dev Summit had shout-out for Flipkart for implementing this feature on Chrome. I explored chrome://serviceworker-internals to see Facebook and Medium are using it as well.

Happy coding Service Worker in the new year 2016 !

Code repo for sample app.