Strumenti Utilizzati
Cordova e plugins
Per realizzare l'applicazione è stato utilizzato Cordova 3.4.1 ed i seguenti plugin:
-
SocialSharing-PhoneGap-Plugin: per permettere all'utente di condividere le foto scattate. Documentazione ed ulteriori informazioni sono presenti presso la repository github: https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin
-
Cordova Camera Plugin: per avere accesso alla fotocamera del dispositivo. Documentazione e ulteriori informazioni nella pagina del plugin: http://plugins.cordova.io/#/package/org.apache.cordova.camera
-
Cordova Device Plugin: fornisce una descrizione dell'hardware e del software del dispositivo. Documentazione e ulteriori informazioni nella pagina del plugin: http://plugins.cordova.io/#/package/org.apache.cordova.device
-
Cordova File Plugin: per memorizzare nel filesystem locale le foto scattate. Documentazione e ulteriori informazioni nella pagina del plugin: http://plugins.cordova.io/#/package/org.apache.cordova.file
-
Cordova Geolocation Plugin: utilizzato per determinare la posizione dell'utente. Documentazione e ulteriori informazioni nella pagina del plugin: http://plugins.cordova.io/#/package/org.apache.cordova.geolocation
-
Cordova StatusBar Plugin: permette di avere un'integrazione con la status bar di iOS 7, in modo tale da avere stesso look and feel di una applicazione nativa. Documentazione e ulteriori informazioni nella pagina del plugin: http://plugins.cordova.io/#/package/org.apache.cordova.statusbar
Ionic
Per realizzare l'interfaccia grafica dell'applicazione è stato utilizzato Ionic un framework open source che utilizza AngularJS e fornisce agli sviluppatori componenti (HTML/CSS/Javascript) ed alcuni plugin per Cordova.
I componenti forniti hanno directive da utilizzare nelle view HTML ed API da utilizzare nei corrispondenti controller.
Ionic è un framework molto giovane e nella fase iniziale di realizzazione e diffusione. All'inizio della scrittura dell'applicazione era stata da poco pubblicata la prima beta pubblica, ad oggi è stata rilasciata l'ottava beta.
Per l'applicazione sono stati utilizzati i seguenti componenti:
-
ion-nav-view: componente che tiene traccia della storia di navigazione dell'utente all'interno dell'applicazione fornendo animazioni tra una view ed un altra. Utilizza UI-Router per gestire i vari stati dell'applicazione
-
ion-nav-bar: per gestire l'header di ogni view inserendo titoli e pulsanti personalizzati
-
ion-tabs: permette di utilizzare il layout a tabs ed associare ad ogni tab una diversa view
-
ionicPopup: per notificare l'utente
Estensioni AngularJS
Sono state utilizzate anche le seguenti estensioni per AngularJS:
-
Angular-google-maps : fornisce directives per utilizzare google maps all'interno di applicazioni AngularJS
-
Bindonce: utilizzato quando possibile per aumentare le performances
A causa del two-way data binding di AngularJS la directive ng-repeat causa dei deterioramenti di performances poiché AngularJS utilizza un meccanismo di dirty checking per controllare se sono state effettuate modifiche negli elementi. Utilizzando bindonce viene “rotto” questo two-way data binding per gli elementi specificati, e dopo aver fatto il rendering degli elementi nella view AngularJS non controllerà eventuali modifiche. Ho utilizzato bindonce nella lista di fotografie visualizzate nella timeline.
Le future versioni di AngularJS dovrebbero utilizzare il metodo Object.observe() (non ancora supportato da tutti i browser) per velocizzare queste operazioni ed il team di AngularJS ha dichiarato di avere già ottenuto un incremento delle prestazioni del 20-40%.
Struttura dell'Applicazione
Per la scrittura dell'applicazione ho realizzato tre moduli AngularJS, ed ho organizzato i file in modo da avere un componente in ogni file separato, definendo tutti i moduli nel file app.js e utilizzando il metodo getter angular.module per ottenere in ogni file il modulo desiderato per poterci definire e registrare il componente.
Services
Il modulo MyPhotoDiary.services è così definito:
angular.module('MyPhotoDiary.services',[]);
ed è richiamato in ogni file dentro la cartella scripts/services per poter definire un servizio in ogni file separato (ho fatto questa scelta perché preferisco avere più file piccoli ognuno con limitate responsabilità, poi in fase di deploy i fari file .js vengono concatenati e il file risultante viene minimizzato).
Ho realizzato i seguenti services:
-
CameraService: utilizza le API fornite dal plugin Cordova Camera per scattare fotografie e restituisce una promise.
-
GeolocationService: utilizza le API fornite dal plugin Cordova Geolocation plugin per ottenere le coordinate gps e restituisce una promise.
-
PicturesService: gestisce le fotografie dell'applicazione, utilizza il local storage per memorizzare la descrizione, il path su filesystem locale e le coordinate di ogni fotografia. Per l'operazione di rimozione di fotografie dal filesystem utilizza StorageSettings e restituisce una promise (le operazioni su filesystem in Javascript sono asincrone). Emette l'evento NewPicture nel rootScope ogni volta viene realizzata una nuova fotografia, in modo tale che qualunque controller possa gestire questo evento una volta sottoscritto. (AngularJS rilascia un controller se l'utente non è nella view correlata, quindi ho scelto di avere un evento globale in modo tale da poterlo gestire indipendentemente dalla view corrente dell'utente)
-
SettingsService: memorizza le preferenze dell'utente utilizzando il local storage.
-
StorageService: utilizza le API fornite dal plugin Cordova File e permette di memorizzare e cancellare le foto dal filesystem. Per le operazioni restituisce una promise.
Controllers
Il modulo MyPhotoDiary.controllers è così definito:
angular.module('MyPhotoDiary.controllers',['MyPhotoDiary.services']);
e come già spiegato per il modulo relativo ai services è richiamato all'interno di ogni file dentro la cartella scripts/controllers.
Sono stati realizzati i seguenti controllers:
-
MainNavBarController: gestisce la navigation bar presente in ogni view dove è situato un pulsante per scattare una nuova fotografia. Utilizza i services CameraService, GeolocationService, PicturesService e StorageService per catturare e memorizzare la nuova foto. Usa anche ionicModal per mostrare all'utente una view modale per confermare o scartare la nuova fotografia, e ionicPopup per visualizzare eventuali errori.
-
PlacesController: responsabile della creazione dei marker da visualizzare sulla mappa. Utilizza GeolocationService per centrare la mappa nella posizione corrente dell'utente e PicturesService per recuperare le foto da mostrare. Gestisce l'evento NewPicture reindirizzando l'utente alla view timeline.
-
SettingsController: permette di scegliere di quante foto effettuare il prefetch per essere visualizzate nella timeline e di eliminare tutte le foto dell'applicazione. Usa PicturesService per calcolare quante fotografie sono attualmente memorizzate e SettingsService per memorizzare le preferenze utente. Anche questo controller gestisce l'evento NewPicture reindirizzando l'utente alla view timeline.
-
TimelineController: gestisce la condivisione e l'eliminazione della foto selezionata nella corrispettiva view. Inoltre carica ulteriori foto e le aggiunge alla view non appena l'utente raggiunge il limite inferiore dello scroll della view, infatti per motivi di performances non vengono immediatamente caricate tutte le foto nella view. Utilizza PicturesService per richiedere le foto memorizzate mano a mano che l'utente scorre la view e SettingsService per sapere di quante foto effettuare il prefetch. Gestisce l'evento NewPicture aggiungendo la nuova foto in cima alla view e tornando all'inizio della lista di fotografie.
Modulo Principale
Il modulo MyPhotoDiary è così definito:
angular.module('MyPhotoDiary', ['ionic', 'MyPhotoDiary.controllers', 'MyPhotoDiary.services', 'google-maps', 'pasvaz.bindonce'])
Ed è configurato nel file app.js specificando per ogni stato dell'applicazione (usando UI-router) il rispettivo nome, url, template e controller.
Gli stati sono:
-
tabs: stato astratto con template tabs.html. Uno stato astratto non può mai essere direttamente attivato, sarà attivato uno dei suoi stati figli.
-
tabs.places: mappato su url /places utilizza il template places.html ed il controller PlacesController.
-
tabs.timeline: mappato su url /timeline utilizza il template timeline.html ed il controller TimelineController.
-
tabs.settings: mappato su url /settings utilizza il template settings.html ed il controller SettingsController.
All'avvio dell'applicazione viene impostato lo stato tabs.timeline.
Views
I template di ogni view sono salvati all'interno della cartella templates e sono:
-
confirmPhoto: una view modale che visualizza la foto appena scattata e permette di confermarne il salvataggio oppure di scartarla. E' gestita da MainNavController.
-
places: contiene una google map e un marker per ogni foto nella posizione in cui è stata scattata. Utilizza la direttiva <google-map> fornita da Angular-google-maps.
-
settings: attraverso uno slider permette all'utente di scegliere il numero di fotografie di cui fare il prefetch nella timeline ed ha un pulsante per eliminare tutte le foto memorizzate.
-
tabs: visualizza tre tab, uno per lo stato tabs.timeline, uno per lo stato tabs.places e uno per lo stato tabs.settings.
-
timeline: mostra in una lista in ordine cronologico le fotografie memorizzate e sotto ognuna di essa due pulsanti, uno per eliminare ed uno per condividere la foto.
Tools
Per velocizzare il workflow sono stati utilizzati alcuni tool open source, ne riporto in seguito una breve descrizione per ognuno.
Grunt
Grunt è un task runner scritto in Javascript con molti tool e plugin resi disponibili dalla comunità, tra i plugin che ho utilizzato segnalo bower-install che inietta le dipendenze gestite con bower nell'HTML, concat che concatena diversi fine in uno unico, ngmin, cssmin che comprime i file CSS, uglify per minimizzare il codice Javascript e htmlmin che minimizza il codice HTML.
È installabile attraverso npm (package manager di node.js).
Bower
Bower è un package manager per componenti web. Per l'applicazione ho specificato come dipendenze AngularJS, Ionic, angular-google-maps e angular-bindonce. Bower provvede a scaricare in locale l'ultima versione (o la versione specificata) delle dipendenze, in modo tale che facendone il deploy sul dispositivo non sia necessaria alcuna connettività di rete.
Anche bower è reperibile attraverso npm.
Yeoman
Yeoman è un tool che automatizza il setup degli strumenti precedentemente descritti grazie a generator resi disponibili dalla comunità (attualmente ci sono più di 700 generators).
Per lo sviluppo di questa applicazione ho utilizzato il generator chiamato generator-ionic disponibile in maniera open source all'indirizzo https://github.com/diegonetto/generator-ionic.
Anche yeoman è reperibile con npm.
- angularjs (2) ,
- applicazioni ibride (4) ,
- bower (1) ,
- cordova (1) ,
- grunt (1) ,
- ionic (1) ,
- javascript (3) ,
- yeoman (1)