HTML5 Mobile App: tecniche per il salvataggio dei dati

L’idea di scrivere questo post nasce dalle numerose richieste che ho ricevuto su come comportarsi per il salvataggio dei dati di una HTML5 mobile app, le quali presentavano diversi dubbi e perplessità.

Tutte le applicazioni mobile hanno in qualche modo a che fare la manipolazione dei dati: in un videogame abbiamo bisogno di salvare informazioni come punteggi e livelli raggiunti, in un’app per un catalogo prodotti devono essere salvati i dati di ogni prodotto, ecc: insomma, nella maggior parte delle applicazioni abbiamo bisogno di salvare le impostazioni base dell’utente, e così via per una lista infinita di possibili casi.

Il fatto è che i dati stanno alla base di qualsiasi web app (come anche di qualsiasi sito web), quindi vanno in qualche modo gestiti.

Vediamo quali sono le possibili tecniche da utilizzare per il salvataggio dei dati.

Salvataggio In-memory

in-memory-storage

Non si tratta di un salvataggio effettivo, in quanto le informazioni sono assegnate all’interno di una variabile e vengono perse ogni volta che la pagina è ricaricata (in termini di una mobile app, ovvero un’applicazione a pagina singola, ogni volta che essa è riavviata).

Questo metodo risulta utile per dei dati statici, aggiornabili quindi solo al rilascio di una nuova versione dell’app, per i quali solitamente l’interfaccia non permette la modifica da parte dell’utente, a meno chè il fatto che tali modifiche vadano perse al prossimo riavvio sia previsto dallo sviluppatore.

Ecco l’esempio di un modulo Javascript che salva in una variabile interna (employees) dei dati statici in-memory relativi a dei dipendenti, rendendone possibile il reperimento per id e la ricerca per nome e cognome tramite dei metodi pubblici (findById e findByName):

var Storage = (function () {


    //assegnazione dei dati statici ad una variabile 
    var employees = [
        {"id": 1, "name": "Mario", "lastname": "Rossi", "occupation": "Presidente e CEO", "telephone": "000 000 000", "email": "test@test.com", "city": "Roma" },
        {"id": 1, "name": "Giacomo", "lastname": "Alberti", "occupation": "Project Manager", "telephone": "000 000 000", "email": "test@test.com", "city": "Milano" },
        {"id": 1, "name": "Francesco", "lastname": "Massi", "occupation": "Content Writer", "telephone": "000 000 000", "email": "test@test.com", "city": "Palermo" },
        {"id": 1, "name": "Franco", "lastname": "Bianchi", "occupation": "Web Designer", "telephone": "000 000 000", "email": "test@test.com", "city": "Napoli" }
    ];

    return {
        findById: function(id) {
            var employee = null;
            var l = employees.length;
            for (var i=0; i < l; i++) {
                if (employees[i].id === id) {
                    employee = employees[i];
                    break;
                }
            }
            return employee;
        },
        findByName: function(searchKey) {
            var results = [];
            var l = employees.length;            
            for (var i=0; i < l; i++) {
                var fullName = employees[i].name + " " + employees[i].lastname;
                if (fullName.toLowerCase().indexOf(searchKey.toLowerCase()) > -1) {
                    results.push(employees[i]);
                }
            }
            return results;
        }
    }

}());

Salvataggio tramite localStorage (o web storage)

localStorage

È una funzionalità HTML5 che permette di salvare i dati in modo persistente nel browser.

Ma come funziona esattamente?

Semplicemente, è un modo per qualunque pagina web (e quindi qualunque HTML Mobile App a Pagina singola) di salvare dei dati in locale sotto forma di informazioni in coppie di chiavi e valori. Come i cookie, tali informazioni persistono anche se l’utente naviga su altre pagine web o chiude e riapre il browser (nel nostro caso anche quando l’utente riavvia l’app o ne utilizza altre). A differenza dei cookie però, i dati non vengono trasmessi al server, ma risiedono solamente sul client dell’utente (se non si decida naturalmente di inviarli al server in modo manuale).

Il localStorage è presente nativamente nei browser che lo supportano, quindi per utilizzarlo non abbiamo bisogno di includere plugin esterni o componenti di terze parti, anche se è possibile servirsi di librerie che ne facilitano l’utilizzo.

La maggior parte dei browser mobile supportano il localStorage.

Ecco l’esempio del modulo dei dipendenti adattato per l’utilizzo del localStorage, in cui tutto l’array viene salvato come stringa in corrispondenza della chiave “employees”:

var Storage = (function () {

    window.localStorage.setItem("employees", JSON.stringify(
        [
        {"id": 1, "name": "Mario", "lastname": "Rossi", "occupation": "Presidente e CEO", "telephone": "000 000 000", "email": "test@test.com", "city": "Roma" },
        {"id": 1, "name": "Giacomo", "lastname": "Alberti", "occupation": "Project Manager", "telephone": "000 000 000", "email": "test@test.com", "city": "Milano" },
        {"id": 1, "name": "Francesco", "lastname": "Massi", "occupation": "Content Writer", "telephone": "000 000 000", "email": "test@test.com", "city": "Palermo" },
        {"id": 1, "name": "Franco", "lastname": "Bianchi", "occupation": "Web Designer", "telephone": "000 000 000", "email": "test@test.com", "city": "Napoli" }
        ]
    ));


    return {

        findById: function(id) function (id) {

            var employees = JSON.parse(window.localStorage.getItem("employees")),
                employee = null,
                l = employees.length;

            for (var i = 0; i < l; i++) {
                if (employees[i].id === id) {
                    employee = employees[i];
                    break;
                }
            }
            return employee;
        },

        findByName: function(searchKey) {
            var results = [],
                employees = JSON.parse(window.localStorage.getItem("employees")),
                l = employees.length;            
            for (var i=0; i < l; i++) {
                var fullName = employees[i].name + " " + employees[i].lastname;
                if (fullName.toLowerCase().indexOf(searchKey.toLowerCase()) > -1) {
                    results.push(employees[i]);
                }
            }
            return results;
        },

        //aggiunge all'array un nuovo dipendende, che quindi rimarrà salvato in locale 
        //e resterà disponibile anche al prossimo riavvio
        addItem: function(itemJson) {
            var employees = JSON.parse(window.localStorage.getItem("employees"));
            employees.push(itemJson);
            window.localStorage.setItem("employees", JSON.stringify(employees));
        }

    }

}());

Come puoi vedere dal codice, abbiamo inserito il metodo pubblico “addItem”, che permette di aggiungere un nuovo dipendente alla lista, il quale rimarrà disponibile insieme agli altri anche al riavvio dell’applicazione.

Il localStorage è il metodo che ho usato per il salvataggio delle località e delle impostazioni nella mia applicazione mobile Tint Weather e per il salvataggio dei dati inerenti le informazioni fisiche dell’utente in Dieta Si o No?, pubblicate entrambe su App Store (puoi saperne di più in questa pagina).

tint-localStorage

Se anche tu vuoi realizzare e distribuire la tua applicazione, forse ti potrebbe interessare l’e-book che sto scrivendo: HTML Mobile Accelerato.

Salvataggio con un Database HTML

websql

Come probabilmente saprai, è possibile usare due tipi diversi di database lato client in HTML:

  1. Web Sql: mette a disposizione un modello di API che permettono di eseguire delle query tramite una variante di linguaggio SQL.

  2. IndexedDB: definisce un modello API con le quali è possibile lavorare in un database di record con valori semplici ed oggetti gerarchici.

In entrambi i casi i dati vengono salvati esclusivamente lato client, sul dispositivo (desktop o mobile) dell’utente.

Purtroppo in questo tipo di salvataggio ci troviamo in una terra ancora instabile:

  1. da un lato il WebSql non sarà più mantenuto in modo attivo dalle spec dello staff W3C, il che vuol dire che in futuro sarà abbandonato; ecco il link alla pagina ufficiale w3c.

  2. dall’altro l’IndexedDB non è ancora supportato da Mobile Safari per iOS, che invece supporta il WebSql (puoi consultare la tabella delle compatibilità di IndexedDB a questo link); ecco il link alla pagina ufficiale w3c.

Siccome stiamo parlando di Mobile App, e siccome il numero di dispositivi Apple rappresenta una grandissima fetta del mercato, è ancora opportuno servirsi del WebSql per salvare i nostri dati, dirigendosi poi verso l’utilizzo dell’IndexedDB nel momento in cui esso sarà supportato da tutti i dispositivi.

Anche le versioni desktop di Chrome, Safari e Opera supportano il WebSql, il che ci dà la possibilità di testare eventualmente il salvataggio dati anche su tali tipi di browser, evitando di effettuare ogni volta il deploy nel dispositivo.

Vediamo l’esempio del modulo javascript dei dipendenti adattato per il WebSql:

var Storage = (function () {


    //funzione che crea una nuova tabella 'employee' per i dipendenti, 
    //si serve della transazione passata come parametro
    var createTable = function (tx) {
        //se la tabella è già esistente la rimuoviamo (decommentare questa riga per ripristinare il database)
        //tx.executeSql('DROP TABLE IF EXISTS employee');

        //creiamo la nuova tabella
        var sql = "CREATE TABLE IF NOT EXISTS employee ( " +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "name VARCHAR(50), " +
            "lastname VARCHAR(50), " +
            "occupation VARCHAR(50), " +
            "telephone VARCHAR(50), " +
            "city VARCHAR(50), " +
            "email VARCHAR(50))";

        //le API WebSql funzionano in modo asicncrono, per cui è sempre bene gestire il 
        //caso di operazione effettuata con successo o di errore
    
        tx.executeSql(sql, null,
            function () {
                console.log('Tabella creata con successo');
            },
            function (tx, error) {
                alert('Errore nella creazione della tabella : ' + error.message);
            }
        );
    };

    //funzione che popola la tabella "employee" con dati iniziali,
    //si serve della transazione passata come parametro
    var addData = function (tx, data) {

        var employees = [];
        
        //se i dati sono stati passati nel parametro 'data' li aggiungo a quelli esistenti, altrimenti aggiungo dei dati fittizi
        if(data)
            employees = data;
        else
            employees = [
                {"id": 1, "name": "Mario", "lastname": "Rossi", "occupation": "Presidente e CEO", "telephone": "000 000 000", "email": "test@test.com", "city": "Roma" },
                {"id": 1, "name": "Giacomo", "lastname": "Alberti", "occupation": "Project Manager", "telephone": "000 000 000", "email": "test@test.com", "city": "Milano" },
                {"id": 1, "name": "Francesco", "lastname": "Massi", "occupation": "Content Writer", "telephone": "000 000 000", "email": "test@test.com", "city": "Palermo" },
                {"id": 1, "name": "Franco", "lastname": "Bianchi", "occupation": "Web Designer", "telephone": "000 000 000", "email": "test@test.com", "city": "Napoli" }
            ];

        var l = employees.length;
        var sql = "INSERT OR REPLACE INTO employee " +
            "(id, name, lastname, occupation, telephone, city, email) " +
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        var e;
        for (var i = 0; i < l; i++) {
            e = employees[i];
            tx.executeSql(sql, [e.id, e.name, e.lastname, e.occupation, e.telephone, e.city, e.email],
                function () {
                    console.log('Inserimento effettuato con successo');
                },
                function (tx, error) {
                    alert('Errore inserimento: ' + error.message);
                }
            );
        }
    };


    //inizializzazione db:


    //creo una nuova variabile con l'istanza del database "WebSql", passando il nome, la versione e le dimensioni.
    var db = window.openDatabase("EmployeeDB", "1.0", "Employee DB", 200000);

    //ottengo l'oggetto transaction tramite una chiamata asincrona, con relative funzioni di gestione per il successo o l'errore
    db.transaction(
        function (tx) {
            //una volta ottenuto l'oggetto transaction (variabile 'tx'),
            //richiamo le due funzioni per la creazione ed il popolamento della tabella 'employee'
            createTable(tx);
            addData(tx);
        },
        function (error) {
            console.log('Errore di transazione: ' + error);
        },
        function () {
            console.log('Transazione ottenuta con successo');
        }
    );


    return {


        //siccome la transaction funziona in modo asincrono, a questa abbiamo aggiunto i due parametri CB ed errorCB 
        //per definire rispettivamente le callback si successo e di errore 
        findById: function(id, CB, errorCB){

            db.transaction(
                function (tx) {

                    var sql = "SELECT id, name, lastname, occupation, city, telephone, email " +
                            "FROM employee WHERE id=:id";

                    tx.executeSql(sql, [id], function (tx, results) {
                        var results = results.rows.length === 1 ? results.rows.item(0) : null;
                        //se la callback di successo è stata passata, la richiamiamo passando come parametro il risultato della query
                        if(CB) CB(results);
                    });
                },
                function (error) {
                    //se la callback di errore è stata passata, la richiamiamo passando come parametro il messaggio di errore
                    if(errorCB) errorCB(error.message);
                }
            );

        },

        //analogamente al metodo 'findById' eseguiamo la query in modo asincrono 
        //passando anche qui le due callback di successo ed errore
        findByName: function(searchKey, CB, errorCB) {


            db.transaction(
                function (tx) {

                    var sql = "SELECT id, name, lastname, occupation, city, telephone, email " +
                        "FROM employee WHERE name || ' ' || lastname LIKE ? ";

                    tx.executeSql(sql, ['%' + searchKey + '%'], function (tx, results) {

                        var l = results.rows.length, 
                            employees = [],  
                            i = 0;

                        for (; i < l; i = i + 1){
                            employees.push(results.rows.item(i));
                        }
                        
                        //se la callback di successo è stata passata, la richiamiamo passando come parametro il risultato della query
                        if(CB) CB(employees);
                    });
                },
                function (error) {
                    //se la callback di errore è stata passata, la richiamiamo passando come parametro il messaggio di errore
                    if(errorCB) errorCB(error.message);
                }
            );

        },

        //aggiunge al database uno o più dipendenti, che quindi rimarranno salvati nel database in locale 
        //e resteranno disponibili anche al prossimo riavvio
        addItem: function(newItemsArray) {
            db.transaction(
                function (tx) {
                    //richiamo la funzione per aggiungere dati alla tabella 'employee'
                    addData(tx, newItemsArray);
                },
                function (error) {
                    console.log('Errore di transazione: ' + error);
                },
                function () {
                    console.log('Transazione ottenuta con successo');
                }
            );
        }

    }

}());

Salvataggio su server via Ajax

mobile-ajax-storage

I metodi di salvataggio fin qui esposti sono molto utili per salvare dei tipi di dati che non devono essere accessibili da nessun’altro a parte l’utente stesso, e che non possono essere consultati da più di un dispositivo, ma differiscono da un dispositivo all’altro.

Tuttavia, nella maggior parte dei casi lo sviluppatore (o chi distribuisce il prodotto in questione) ha bisogno di accedere ai dati salvati dall’utente, renderli disponibili ad altri e fruibili anche su diversi dispositivi (magari attraverso un riconoscimento per account) ecc.

Molte volte abbiamo bisogno di salvare oppure ottenere i dati di una mobile App da un server remoto; che sia attraverso un servizio API creato ad-hoc per la nostra app o un servizio standard di terze parti non fa alcuna differenza, il procedimento è sempre quello di affidarsi a chiamate al server, ovvero alle famose chiamate Ajax.

Qualsiasi Single-Page-Application (e quindi ogni HTML5 Mobile App) comunica in remoto esclusivamente con chiamate Ajax, per due motivi principali:

  1. la pagina non deve mai essere ricaricata e quindi il DOM va modificato e manipolato a seconda delle esigenze, invece che richiamare diverse pagine come in un normale sito web

  2. si tratta di applicazioni che funzionano interamente grazie a codice lato-client, e quindi non possono usare i classici linguaggi lato server come php, asp, asp.net, C# ecc. per generare codice e dinamico e parti dell’interfaccia.

In un precedente articolo ho parlato di come gestire la connessione di rete per le chiamate Ajax nel caso di una HTML5 Mobile App:

  1. HTML5 Mobile App: come gestire la connessione di rete

In questo tipo di salvataggio (o reperimento dei dati) il “il lavoro sporco” viene effettuato sul server (attraverso linguaggi come php, asp, asp.net, C# ecc.), per cui lo sviluppatore dell’applicazione deve solamente preoccuparsi di inviare i dati da salvare ad un url remoto ed attendere eventualmente la risposta di avvenuto inserimento (ovvero la callback); per la richiesta dati è ancora più semplice: basterà richiamare solamente l’url (con eventuali parametri aggiuntivi del caso) ed attendere la risposta con i risultati.

Ovviamente è inutile sottolineare che il più delle volte, soprattutto nel caso di sviluppatori autonomi, entrambe le parti client e server sono gestite dalla stessa persona.

Vediamo l’esempio del modulo javascript dei dipendenti adattato ad un tipo di salvataggio in remoto:

var Storage = (function () {

    var url = 'https://www.urlremoto.it';

    return {

        //Visto che la chiamata ajax è asincrona, alla funzione 'findById' questa abbiamo aggiunto i due parametri CB ed errorCB 
        //per definire rispettivamente le callback si successo e di errore 
        findById: function(id, CB, errorCB){

            $.ajax({
                url: url + "/" + id, //il servizio di API remoto eseguirà la query con l'id preso dall'url
                dataType: "jsonp",
                success: function(result){
                    //se la callback di successo è stata passata, la richiamiamo 
                    //passando come parametro il risultato restituito dal server
                    if(CB) CB(result);  
                },
                error:function(xhr){
                    //se la callback di errore è stata passata, la richiamiamo 
                    //passando come parametro il messaggio di errore
                    if(errorCB) errorCB(xhr.status+": "+xhr.responseText);
                }
            });

        }, url: url + "?name=" + searchKey

        //analogamente al metodo 'findById' eseguiamo la chiamata ajax
        //passando anche qui le due callback di successo ed errore
        findByName: function(searchKey, CB, errorCB) {

            $.ajax({
                url: url + "?s=" + searchKey, //il servizio di API remoto eseguirà la query cercando il termine preso dall'url
                dataType: "jsonp",
                success: function(results){
                    //se la callback di successo è stata passata, la richiamiamo 
                    //passando come parametro i risultati restituiti dal server
                    if(CB) CB(result);    
                },
                error:function(xhr){
                    //se la callback di errore è stata passata, la richiamiamo 
                    //passando come parametro il messaggio di errore
                    if(errorCB) errorCB(xhr.status+": "+xhr.responseText);
                }
            });

        },
        //invia al servizio remoto uno o più dipendenti da salvare, che quindi rimarranno salvati nel database in remoto 
        //e resteranno disponibili anche al prossimo riavvio, effettuando una chiamata allo stesso servizio
        addItem: function(newItemsArray) {
            $.ajax({
                url: url, 
                dataType: "jsonp",
                //passiamo come parametro il json dei nuovi dipendenti sotto forma di stringa
                data:{
                    employees: JSON.stringify(newItemsArray)
                },
                success: function(results){
                    //richiamiamo la callback di successo, se è stata passata
                    if(CB) CB(result);    
                },
                error:function(xhr){
                    //se la callback di errore è stata passata, la richiamiamo 
                    //passando come parametro il messaggio di errore
                    if(errorCB) errorCB(xhr.status+": "+xhr.responseText);
                }
            });
        }

    }

}());

Conclusioni

Come puoi vedere esistono diversi modi per salvare i dati di un’applicazione e poterne usufruire in qualunque momento.

Ma quali utilizzare?

Ciò dipende dalle tue esigenze:

  • se ti basta rendere fruibili dei dati statici che non vengono alterati dall’utente, o che comunque non necessitano di mantenere eventuali modifiche al prossimo riavvio, allora non ti basterà il salvataggio in-memory

  • se il tuo intento è solamente quello di salvare delle semplici preferenze dell’utente per un solo dispositivo o tipi di dati non troppo complessi, allora potresti prendere in considerazione il localStorage

  • se invece hai bisogno di una struttura più complessa di dati locali, che preveda una gerarchia, delle relazioni e possa ottenere dei dati incrociati, allora faresti meglio a servirti del WebSql o dell’IndexedDB

  • infine, se vuoi fornire un servizio cloud in cui ogni utente può consultare i propri dati da diversi dispositivi, oppure dei dati che subiscono modifiche anche senza il suo intervento, ti affiderai al salvataggio remoto con chiamate Ajax.

Naturalmente è possibile (e consigliato) implementare nella propria Mobile App anche più tipi di salvataggio dati.

Potrebbe essere per esempio il caso di un’ app che ottiene informazioni remote ma permette di configurare alcuni aspetti tramite un pannello di impostazioni locale: in questo caso dovremmo utilizzare delle chiamate Ajax per i dati remoti e il localStorage o il WebSql per salvare i dati delle impostazioni inserire dall’utente.

In altre parole la scelta è a tua discrezione, e dovresti decidere sulla base di due aspetti molto importanti per una buona user-experience, soprattutto nel caso di dispositivi mobile: elevate prestazioni e ottimizzazione dei dati passati tra client e server (ovvero riduzione dei tempi di attesa).

E tu quali tecniche di salvataggio usi di solito? Scrivilo nei commenti !

Tag: , ,

L'autore

Web Designer Freelance e Developer (conosciuto anche come upCreative), si occupa del design e dello sviluppo di applicazioni web dal 2008; è abituato a gestire più ruoli e spaziare su più campi, ma le sue passioni principali sono realizzare interfacce in html5 e CSS3 e testare continuamente nuove tecniche di content marketing. Sta scrivendo un e-book su come realizzare mobile app (https://www.upcreative.net/html-mobile-accelerato/)

Sito web dell'autore | Altri articoli scritti da

Articoli correlati

Potresti essere interessato anche ai seguenti articoli:

5 commenti

Trackback e pingback

  1. HTML5 Mobile App: tecniche per il salvataggio d...
    […] L’idea di scrivere questo post nasce dalle numerose richieste che ho ricevuto su come comportarsi per il salvataggio dei…
  2. Come creare un app in HTML: salvataggio dati con database SQL lato-client – parte 1 | upCreative
    […] In un articolo che ho scritto per YourInspirationWeb.com, avevo riportato le varie modalità con cui è possibile salvare i dati per…
  3. HTML Mobile App: quali competenze occorrono per crearne una | Your Inspiration Web
    […] HTML5 Mobile App: tecniche per il salvataggio dati […]
  4. Come creare un app: realizzare una view di login con il localStorage | upCreative
    […] localStorage, di cui avevo parlato in questo articolo che ho scritto per YourInspirationWeb.com, è un sistema di salvataggio dati…
  5. Creare un app: ne hai veramente bisogno? | upCreative
    […] delle funzionalità come la geolocalizzazione o il salvataggio dei dati (leggi questo articolo oppure questo per saperne di più)…