Building PWA with Flutter

Majid Hajian

mhadaily

Credit to: JDominik Roszkowski - https://codepen.io/orestesgaolin/pen/ExVboMY

https://chrome-trex-flutter.netlify.app/#/

Credit to: Joshua de Guzman - https://codepen.io/joshuadeguzman/pen/jObrzJB

https://nike-shop-flutter.netlify.app

Credit to: Zoey Fan - https://codepen.io/zoeyfan/pen/ExVaXGK

https://gooey-edge-flutter.netlify.app

mhadaily

Scenarios

A PWA built with Flutter

Single Page Application

Existing Mobile Applications

At this time

mhadaily

Agenda

  • Service Worker, the Brain of PWA
  • Web App Manifest
  • PWA Charactrestics
  • PWA in Flutter
  • DEMO
  • Extending SW with Workbox.js

ME.dart

import 'package:flutter/material.dart';
MaterialApp(
   ThemeData(
        name: "Majid Hajian",
        location: "Oslo, Norway",
        description: '''
                Google Developer Expert
        	Passionate Software engineer, 
	        Community Leader, Author and international Speaker
         ''',
        main: "Flutter/Dart, PWA, Performance",
        homepage: "https://www.majidhajian.com",
        socials: {
          twitter: "https://www.twitter.com/mhadaily",
          github: "https://www.github.com/mhadaily"
        },
        author: {
          Pluralsight: "www.pluralsight.com/authors/majid-hajian",
          Apress: "Progressive Web App with Angular, Book",
          PacktPub: "PWA development",
          Udemy: "PWA development",
        }
        founder: "Softiware As (www.Softiware.com)"
        devDependencies: {
          tea: "Ginger", 
          mac: "10.14+",
        },
        community: {
          MobileEraConference: "Orginizer",
          FlutterVikings: "Orginizer", 
          FlutterDartOslo: "Orginizer",
          GDGOslo: "Co-Orginizer",
          DevFestNorway: "Orginizer",
          ...more
        }));

mhadaily

Find me on the internet by

https://fluttervikings.com/

Reliable

Fast

Engaging

Reliable

Linkable  

Engaging

Secure

Progressive by nature

Native-like User Experince

Responsiveness

Fast

Discoverable

mhadaily

mhadaily

mhadaily

Service Worker 

Web App Manifest

mhadaily

mhadaily

mhadaily

mhadaily

mhadaily

mhadaily

self.addEventListener("install", (event) => {


}


self.addEventListener("activate", function(event) {


}


self.addEventListener("fetch", (event) => {



}

mhadaily

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));


https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

mhadaily

// Cache API
caches.open(cacheName)
.then(function(cache) {
      return cache.addAll(
        [
          '/css/bootstrap.css',
          '/css/main.css',
          '/js/bootstrap.min.js',
          '/js/jquery.min.js',
          '/offline.html'
        ]
      );
})

https://developer.mozilla.org/en-US/docs/Web/API/Cache

mhadaily

mhadaily

mhadaily

Web App Manifest

The Web App Manifest is a JSON text file following Web App Manifest specification

that provides information about an application such as its name, author, icons, and description.

mhadaily

<link rel="manifest" href="/app.webmanifest">



<link rel="manifest" href="/manifest.json">

mhadaily

{
  "short_name": "Flutter",
  "name": "Flutter: Amazing Technology!",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/?source=pwa",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#3367D6",
  "description": "Flutter pwa information",
}

mhadaily

mhadaily

  • Progressive Web App

mhadaily

Dart to JS

https://dart.dev/tools/dart2js

 dart2js -O2 -o test.js test.dart

https://developers.google.com/web/tools/workbox

npm install workbox-cli --global
  Examples:
    $ workbox wizard
    $ workbox wizard --injectManifest
    $ workbox generateSW --watch
    $ workbox injectManifest configs/workbox-dev-config.js
    $ workbox copyLibraries build/
module.exports = {
  globDirectory: 'build/web/',
  globPatterns: ['**/*.{json,otf,ttf,js,wasm,png,html}'],
  swSrc: 'sw.js',
  swDest: 'build/web/sw.js',
  maximumFileSizeToCacheInBytes: 10000000,
};

workbox-config.js

import { precacheAndRoute } from 'workbox-precaching';

precacheAndRoute(self.__WB_MANIFEST);
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-sw.js');

const { precacheAndRoute } = workbox.precaching;

precacheAndRoute(self.__WB_MANIFEST);

sw.js

flutter build web --pwa-strategy none
workbox injectManifest workbox-config.js

then

   <script>
      var scriptLoaded = false;
      .....

var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
      
      ....
    </script>

index.html

   <script>
      var scriptLoaded = false;

      .....

var serviceWorkerUrl = 'sw.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
      
      ....
      
    </script>
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-sw.js');

const googleAnalytics = workbox.googleAnalytics;
const { precacheAndRoute } = workbox.precaching;

precacheAndRoute(self.__WB_MANIFEST);
googleAnalytics.initialize();
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-sw.js');
const googleAnalytics = workbox.googleAnalytics;
const { precacheAndRoute } = workbox.precaching;
const { ExpirationPlugin } = workbox.expiration;
const { CacheableResponsePlugin } = workbox.cacheableResponse;
const { registerRoute } = workbox.routing;
const { StaleWhileRevalidate, CacheFirst } = workbox.strategies;
// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.
registerRoute(
  ({ url }) => url.origin === 'https://fonts.googleapis.com',
  new StaleWhileRevalidate({
    cacheName: 'google-fonts-stylesheets',
  })
);
// Cache the underlying font files with a cache-first strategy for 1 year.
registerRoute(
  ({ url }) => url.origin === 'https://fonts.gstatic.com',
  new CacheFirst({
    cacheName: 'google-fonts-webfonts',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
      new ExpirationPlugin({
        maxAgeSeconds: 60 * 60 * 24 * 365,
        maxEntries: 30,
      }),
    ],
  })
);
registerRoute(
  ({ url }) => url.origin === 'https://hacker-news.firebaseio.com',
  new CacheFirst({
    cacheName: 'stories',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 5 * 60, // 5 minutes
      }),
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  })
);

Demo

<script type="module">
      function createUIPrompt(opts) {
        if (
          confirm('New version of the application is downloaded, do you want to update? May take two reloads.')
        ) {
          opts.onAccept();
        }
      }
      import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-window.prod.mjs';
      if ('serviceWorker' in navigator) {
        const wb = new Workbox('/sw.js');
        let registration;

        const showSkipWaitingPrompt = (event) => {
          const prompt = createUIPrompt({
            onAccept: () => {
              wb.addEventListener('controlling', (event) => {
                window.location.reload();
              });

              wb.messageSkipWaiting();
            },

            onReject: () => {
              prompt.dismiss();
            },
          });
        };
        wb.addEventListener('waiting', showSkipWaitingPrompt);
        wb.register();
        loadMainDartJs();
      } else {
        loadMainDartJs();
      }
</script>

index.html

addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

sw.js

Navigator 2.0

Summary

  • Servier Worker / Fetch / Caching
  • Web App Manifest
  • What the fundamentails in PWA
  • Read more for advanced patterns: https://slides.com/mhadaily/pwa-advanced-patterns-and-strategies

Majid Hajian

mhadaily

Slides and link to source code

slides.com/mhadaily

SVG icons credited to undraw.co