Creating app like experiences with Progressive Web Apps

Alex Bularca

Frontend Architect - EveryMatrix

twitter: @alexbularca

Github: arcade

What exactly are these progressive web apps?

A Progressive Web App uses modern web capabilities to deliver an app-like user experience. They evolve from pages in browser tabs to immersive, top-level apps, maintaining the web's low friction at every moment.

Progressive Web Apps are:

  • Progressive - Work for every user, regardless of browser choice because they’re built with progressive enhancement as a core tenet.
  • Responsive - Fit any form factor, desktop, mobile, tablet, or whatever is next.
  • Connectivity independent - Enhanced with service workers to work offline or on low quality networks.
  • App-like - Use the app-shell model to provide app-style navigations and interactions.
  • Discoverable - Are identifiable as “applications” thanks to W3C manifests and service worker registration scope allowing search engines to find them.
  • Re-engageable - Make re-enngagement easy through features like push notifications.
  • Installable - Allow users to “keep” apps they find most useful on their home screen without the hassle of an app store.
  • Linkable - Easily share via URL and not require complex installation.

Easy reengagement

Motivation

Problems?

  • Not easily discoverable
  • Don't exactly live on the web
  • Involve going to a store (sometimes multiple) to get the app
  • Slow iteration pace
  • Complex packaging

25

 

average apps used per month by a mobile user

100+

web sites navigated per month by the average mobile user

Fiksu CPI Index

Installs have a cost

In a consumer mobile app, every step you make a user perform before they get value out of your app will cost you 20% of users.

So how do we do it?

  • WebApp manifests
  • Service Workers
  • Add to homescreen application banner
  • Push notifications for re-engagement

WebApp Manifest

The Manifest for Web applications is a simple JSON file that gives you, the developer, the ability to control how your app appears to the user in the areas that they would expect to see apps (for example the device home screen), direct what the user can launch and more importantly how they can launch it

Sample

{
  "name": "My Funky APP",
  "short_name": "MFA",
  "icons": [{
        "src": "images/touch/icon-128x128.png",
        "sizes": "128x128",
        "type": "image/png"
      }, {
        "src": "images/touch/apple-touch-icon.png",
        "sizes": "152x152",
        "type": "image/png"
      }, {
        "src": "images/touch/ms-touch-icon-144x144-precomposed.png",
        "sizes": "144x144",
        "type": "image/png"
      }, {
        "src": "images/touch/chrome-touch-icon-192x192.png",
        "sizes": "192x192",
        "type": "image/png"
      }],
  "start_url": "/index.html?homescreen=1",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

App install banners

How do I get them?

  • Have a WebApp Manifest
  • Be served over HTTPS (see letsencrypt for a free certificate)
  • Have a valid service worker registered
  • Be visited twice, with at least 5 minutes between visits

Service Workers

A service worker is a script that runs in the background of your page and responds to events and network requests.

Basic architecture

  • The service worker URL is fetched and registered via serviceWorkerContainer.register().
  • If successful, the service worker is executed.
  • When the user navigates to a page controlled by the service worker it is installed
  • After instalation the service worker is activated (this is a good place for cleanup of previously cached resources).
  • The service worker now has control of pages opened after the register() call

Registering a service worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {
    // registration worked
    console.log('Registration succeeded. Scope is ' + reg.scope);
  }).catch(function(error) {
    // registration failed
    console.log('Registration failed with ' + error);
  });
};

Lets do some caching

this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request);
  );
});

Other options for responding to requests

// respond with a custom reply
new Response('<p>Hello from your friendly neighbourhood service worker!</p>', {
  headers: { 'Content-Type': 'text/html' }
});

// go out to the network to fetch the actual data in case we couldn't generate a reply
fetch(event.request);

// or maybe go to a fallback in case we can't access the network
caches.match('/fallback.html');

Going for a cache first approach

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).catch(function() {
      return fetch(event.request).then(function(response) {
        return caches.open('v1').then(function(cache) {
          cache.put(event.request, response.clone());
          return response;
        });  
      });
    })
  );
});


Demo
 

References

Thank you
Questions?

Made with Slides.com