Online WebApp

Facciamo un esempio pratico...

...comande per la cucina!

Offline nel 2016

Quando mai accade?

Nel ferrarese abbiamo molti palazzi storici...

...muri di mattone pieno da 55cm non sono amici del 4G!

I preconcetti delle WebApp

  • La connessione è attendibile
  • La latenza è nulla
  • La banda è infinita
  • La rete è sicura
  • La topologia della rete non cambia
  • Il costo del viaggio del dato è nullo

Ok, no problem!

Installiamo un router e un server locale!

Online WebApp... Lie-Fi

???

???

Come testare in locale

una connessione Lie-Fi?

I browser funzionano così,

possiamo farci poco!

E se non avessimo bisogno di connetterci ad internet per usare la nostra app?

E se potessimo salvare la nostra app

in locale sul dispositivo?

Offline-First!

AppCache

Offline-first è l'unico modo di raggiungere un esperienza 100%* connessa per l'utente

*A patto che il dispositivo sia attendibile.

Meet the ServiceWorker!

Il ServiceWorker è uno script che il browser esegue in background, separato dalla pagina web, aprendo la porta a nuove funzionalità che non richiedono l'interazione dell'utente come caching, background sync e push notifications.

...solo Chrome e FireFox... :'(

...alla peggio possiamo usare il vecchio standard!

AppCache manifest

<html manifest="example.appcache">
  ...
</html>
CACHE MANIFEST
# v1 2011-08-14
# This is another comment
index.html
cache.html
style.css
image1.png

# Use from network if available
NETWORK:
network.html

# Fallback content
FALLBACK:
/ fallback.html

...puoi scriverlo tu...

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
    // Registration was successful
    console.log('ServiceWorker registration successful with scope: ', registration.scope);
  }).catch(function(err) {
    // registration failed :(
    console.log('ServiceWorker registration failed: ', err);
  });
}

...puoi scriverlo tu...

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

...o lasciarlo fare a

SW-PRECACHE

SW-Precache è un modulo per generare un Service Worker che precacha le risorse statiche, assegnando un hash per ogni file per gestirne il versioning. Si integra con il build tool come webpack o gulp, o può essere eseguito da command line.

SWPrecache si occupa inoltre di scaricare in automatico versioni aggiornate dei contenuti statici e di servirli tramite una logica cache-first.

TIP: Chrome Offline!

TIP: navigator.onLine

  • False quando non sono disponibili connessioni internet
  • True quando POTREBBE essere disponibile, o in casi di Lie-Fi

 

...e per i dati?

Un database locale?

  • IndexedDB
  • WebSQL
  • LocalStorage

LocalForage!

Seleziona in automatico il driver disponibile tra quelli elencati e fornisce delle API comuni simili a quelle offerte dal LocalStorage

localforage.setItem('key', 'value', function (err) {
  // if err is non-null, we got an error
  localforage.getItem('key', function (err, value) {
    // if err is non-null, we got an error. otherwise, value is the value 
  });
});

Lavorare con un database locale ci permette di non preoccuparci dei problemi di connettività...

Le API del LocalStorage sono persino sincrone...

...e memorizzo tutti i dati necessari per usufruire dell'app...

Redux!

Redux per l'Offline First?

  • Lo stato è facilmente serializzabile*
  • Lo stato è facilmente deserializzabile*
  • Lo stato è centralizzato e facilmente accessibile
  • Il reducer si occupa di verificare che lo stato in ingresso sia valido, tutto è quindi ben separato

*a meno di perversioni non "Dan Abramov Approved".

...anche mobx-state-tree va bene!

Sono i vantaggi di uno state tree qualsiasi.

Redux aggiunge la facilità di save-restore senza dover serializzare/deserializzare.

redux-persist

Ad ogni cambiamento, salva in database locale il ramo dello stato desiderato, ed al successivo avvio dell'app lo ripristina dal database locale.

import {persistStore, autoRehydrate} from 'redux-persist'
const store = createStore(reducer, undefined, autoRehydrate())
persistStore(store)

TIP: ServiceWorker Sync

Tramite la ServiceWorker sync posso eseguire il task di sincronizzazione anche in background!

// Register your service worker:
navigator.serviceWorker.register('/sw.js');

// Then later, request a one-off sync:
navigator.serviceWorker.ready.then(function(swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});
self.addEventListener('sync', function(event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

E l'inserimento di nuovi record?

Inserisco e aggiorno lo store locale!

  • L'operazione non risente di problemi di rete
  • E' molto più veloce
  • Migliore esperienza per l'utente

...ma?

  • Niente id progressivi, solo univoci random
    (stringhe)
  • Se voglio gestire un progressivo, dovrò farlo in fase di sincronizzazione
  • Devo effettuare la validazione offline (best practice ma doppio lavoro!)
  • Validazioni online-first problematiche
    (e.g. slug univoco)

TIP: shortid

Genera ID univoci randomici molto corti, url friendly "alla YouTube".

var shortid = require('shortid');

console.log(shortid.generate());
//PPBqWA9

PouchDB?

E' una soluzione production-ready che fornisce un database per il client con sincronizzazione tra database locale e remoto in maniera semplice e standardizzata.

var db = new PouchDB('dbname');

db.put({
  _id: 'dave@gmail.com',
  name: 'David',
  age: 69
});

db.changes().on('change', function() {
  console.log('Ch-Ch-Changes');
});

db.replicate.to('http://example.com/mydb');

[...] I think that sometimes it’s useful to “reinvent the wheel” on your own. [...] This is also the reason because I love the JavaScript ecosystem. It’s true that we have a new ‘shiny’ framework every week, but it’s not a fatigue, it’s a great opportunity to learn new ways to write and organize our code.

@francescostrazzullo

Thanks!

@mattiamanzati

Offline First Application

By mattiamanzati

Offline First Application

  • 2,968