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

Building PWA with Flutter

By Majid Hajian

Building PWA with Flutter

Progressive Web Apps are web applications that have been designed, so they are capable, reliable, and installable. These three pillars transform them into an experience that feels like a platform-specific application. The central core components that ensure that PWA works flawlessly are Service Workers. These components are a network proxy that takes control of a network request from the application and adds cache capabilities, background app sync, push notifications, and offline features. In this talk, I will review the PWA capabilities in Flutter and show you how you can extend these functionalities to have a better progressive web app building with Flutter in order to boost user experience in your application.

  • 1,369