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
A PWA built with Flutter
Single Page Application
Existing Mobile Applications
At this time
mhadaily
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
}));
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
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
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
mhadaily
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],
}),
],
})
);
<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
Majid Hajian
mhadaily
Slides and link to source code
slides.com/mhadaily
SVG icons credited to undraw.co