BLOC SVILUPPO DI UN'APP IOS/ANDROID IN FLUTTER CON STATE MANAGEMENT
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
U NIVERSITÀ DEGLI S TUDI DI NAPOLI F EDERICO II S CUOLA P OLITECNICA E DELLE S CIENZE DI BASE D IPARTIMENTO DI I NGEGNERIA E LETTRICA E T ECNOLOGIE DELL’I NFORMAZIONE C ORSO DI L AUREA IN I NGEGNERIA I NFORMATICA T ESI DI L AUREA IN I NGEGNERIA DEL S OFTWARE S VILUPPO DI UN ’A PP I OS/A NDROID IN F LUTTER CON S TATE M ANAGEMENT BL O C Relatore Candidato Ing. Roberto P IETRANTUONO Enzo Manuel M ANGANO N46/3381 Anno Accademico 2018–2019
Indice 1 Introduzione a Flutter 4 1.1 Cos’è Flutter? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.1 Stateless Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.2 Stateful Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3 Dart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.4 Perché Flutter è basato su Dart? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.5 Material Design (Quantum Paper) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.6 Flutter vs React Native . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2 State Management 8 2.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2 setState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 StatefulBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.4 InheritedWidget & Scoped Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.5 RxDart + BehavoirSubject + GetIt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.6 BLoC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.7 Redux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3 MovieMentor 18 3.1 Descrizione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.1.1 Use Case Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.2 Architettura del Progetto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.2.1 Struttura Server Side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.2.2 Struttura Client Side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.3 User Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.3.1 Logo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.3.2 Schermate App MovieMentor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.4 Server Side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.4.1 Omologazione API Canali TV sotto un formato unico . . . . . . . . . . . . . . . . 23 3.4.2 Inserimento dati nel Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.4.3 MongoDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 1
3.4.4 Algoritmo sulla scelta del Film/SerieTv . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4.5 Invio delle notifiche e memorizzazione . . . . . . . . . . . . . . . . . . . . . . . . 30 3.4.6 node-fcm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.4.7 Creazione API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.5 MovieMentor - Front End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.5.1 Bloc basati su Servizi Esterni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.5.2 Bloc non basati su Servizi Esterni . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 2
Introduzione Negli ultimi anni il concetto di applicazione mobile si è evoluto in modo a dir poco incredibile. Se prima le app mobile rappresentavano esclusivamente un’estensione opzionale del nostro cellulare, oggi ne sono a tutti gli effetti la caratteristica principale. La continua evoluzione delle “app” ha dato il via ad una rivoluzione inarrestabile che ha segnato la nascita di nuove figure lavorative: app developer e app designer. Da quel momento le tecnologie nate per lo sviluppo di applicazioni mobile sono aumentate esponenzial- mente. Oggi, data la vastità di queste nuove tecnologie sempre in continuo aggiornamento, il concetto di “app developer” è diventato fin troppo generico. La figura richiesta dal mercato, non è più solo quella di uno sviluppatore in grado di progettare una nuova “app”, ma di uno sviluppatore in grado di aggiornarla continuamente e manutenerla nel tempo. Dato che per definizione, sviluppare un’app significa paradossal- mente svilupparne due (iOS e Android), aggiornarla significa modificare parallelamente le funzionalità di due codici totalmente diversi tra loro. Queste caratteristiche aumentano moltissimo i costi che le aziende devono sostenere per la produzione di app, rendendo spesso estremamente difficile la possibilità di rivolgersi a due mobile OS (Operating System) sul lungo termine. È da queste esigenze che è nato il concetto di app cross-platform (indipendenti dalla piattaforma). Questa tesi mira a studiare nel dettaglio lo sviluppo di app attraverso Flutter, una tecnologia che negli ultimi tempi è riuscita ad emergere quasi grazie allo stesso slogan che determinò il successo di Java: “write once, run anywhere”. La tesi si suddivide in tre capitoli. Il primo capitolo cerca di fornire una prospettiva dall’alto dei vantaggi principali che offrono Flutter e Dart 1 , introducendo anche simbolicamente il Material Design e la rivali- tà tra Flutter e React Native. Il secondo capitolo ha invece lo scopo di descrivere tutti i possibili pattern architetturali utilizzabili nello sviluppo cross-platform, con particolare attenzione verso il BLoC (Business Logic Component). Flutter rispecchia un’architettura EDA (Event Driven Architecture) che rompe brusca- mente ogni legame con la logica architetturale dettata per anni dal Model View Controller nel mondo delle app. Infine nel terzo capitolo ho deciso di commentare la progettazione e l’implementazione in Flutter e NodeJS dell’app che ho sviluppato per questa tesi: MovieMentor. Lo scopo di questo studio è quello di analizzare l’effettiva maturità che il mondo delle app platform-independent ha acquisito con Flutter e BLoC (il principale pattern architetturale). 1 Il linguaggio di programmazione alla base di Flutter. 3
Capitolo 1 Introduzione a Flutter 1.1 Cos’è Flutter? Flutter è uno UI Framework Open-Source e Cross Platform lanciato da Google nel Maggio 2017 e nato per realizzare App mobile, web e desktop da una singola codebase. Gli aspetti chiave su cui si basa Flutter sono: • Hot Reload: dal punto di vista dello sviluppatore, Flutter offre uno sviluppo delle app più dinamico e veloce. Gli sviluppatori possono effettuare al volo modifiche al codice sorgente e vedere quasi in real-time le modifiche sull’app. Questa funzione aiuta i team ad aggiungere funzionalità, correggere bug e sperimentare nuove idee in un istante. In particolare, l’Hot Reload risulta essere molto utile quando si tratta di app nate da una collaborazione sviluppatore-designer. • Una Codebase, Due Mobile Platform: con Flutter, gli sviluppatori possono scrivere un solo codice base per due applicazioni - sia per iOS che per Android. Flutter è platform-agnostic in quanto ha i pro- pri widget, il che significa che è possibile avere esattamente la stessa applicazione su due piattaforme (è anche possibile avere comportamenti diversi in relazione al differente OS su cui opera). Inoltre va menzionato che Google sta attualmente lavorando anche a "Flutter for Web" e ciò consentirà a breve la possibilità di utilizzare un’unica codebase per coprire Android, iOS e Web. Questa caratteristica porta anche alla realizzazione del 50% di casi di Test di unità in meno (dovuto al fatto di avere una singola codebase, rispetto ad averne due). • Performance Native: le app Flutter funzionano in modo fluido e veloce, pur non essendo ottimizzate per ogni piattaforma. Ciò succede perché Flutter utilizza la libreria grafica Skia Graphics Library. Grazie a Skia, l’interfaccia utente viene renderizzata ogni volta che una View varia (esattamente come nei Game Engine). La maggior parte del lavoro viene eseguita su GPU (graphics processing unit); ecco perché l’interfaccia utente Flutter è fluida e fornisce 60 fps (frame per second). Tuttavia, è necessario fare attenzione durante lo sviluppo, cercando di non renderizzare tutta la View, ma solo la porzione interessata. Renderizzare l’intera View invece dei soli elementi grafici che cambiano, può influenzare moltissimo le prestazioni e la velocità dell’applicazione. 4
1.2 Widgets L’intera logica di Flutter è costruita sul concetto di Widget. I widget Flutter sono realizzati utilizzando una struttura moderna che si ispira a React1 . L’idea centrale è che si costruisce l’interfaccia utente attraverso Widget innestati (che vanno a formare un albero detto Widget-Tree). I widget descrivono come dovrebbe apparire la loro View, data la loro configurazione (descritta dal metodo Build) ed il loro stato attuale. 1.2.1 Stateless Widget Ci sono casi in cui si ha bisogno di widget che dipendono esclusivamente dalla propria configurazione interna: è in questi casi che si introduce uno StatelessWidget. Questo Widget non dipende in alcun modo dallo Stato dell’app. build Widget Figura 1.1: Stateless Widget 1.2.2 Stateful Widget Uno Stateful Widget è un Widget la cui configurazione interna dipende dallo Stato corrente dell’app. Questo ci consente di creare widget che possono mutare dinamicamente la loro forma nel tempo. Esempi tipici di possibili eventi che scaturiscono cambiamenti di stato sono: 1. Input dell’Utente; 2. Risposta Asincrona di Richiesta Web; 3. Handling di Gestures; build setState Widget State Figura 1.2: Stateful Widget 1 React è una libreria JavaScript per la creazione di interfacce utente [22]. 5
1.3 Dart Dart è un linguaggio di programmazione nato ed utilizzato da Google per costruire applicazioni web, server e mobile. A differenza di altri linguaggi di programmazione, Dart ha un proprio package manager chiamato Pub. Nonostante sia stato reso noto al grande pubblico dal 2011, Dart ha iniziato ad attirare l’attenzione nel 2017, quando Google ha annunciato ufficialmente Flutter beta per lo sviluppo di applicazioni mobile cross- platform. Da allora, la popolarità di Dart è drasticamente aumentata andando di pari passo con quella di Flutter. Le sue caratteristiche principali sono: • è un linguaggio fortemente tipizzato. • supporta la Programmazione funzionale, imperativa ed orientata agli oggetti. • supporta ampiamente la programmazione con async/await. 1.4 Perché Flutter è basato su Dart? Negli ultimi anni Google ha dato vita a molti nuovi linguaggi di programmazione tra i quali spiccano sicu- ramente Kotlin e Go. Vale la pena allora chiedersi perché è stato deciso di realizzare Flutter in Dart? Ecco una serie di ragioni alla base di questa scelta. 1. Dart è AOT (Ahead Of Time) compilato in codice nativo veloce e prevedibile, che permette di scrivere quasi tutto il Framework in Dart (la restante parte è scritta in C++). Questo non solo rende Flutter veloce, ma consente anche una forte personalizzazione (inclusi tutti i widget). 2. Dart può anche essere compilato JIT (Just In Time) per cicli di sviluppo estremamente veloci (incluso il popolare hot-reload di Flutter). 3. Dart rende più facile creare animazioni e transizioni fluide che girano a 60-120 FPS. 4. Dart permette a Flutter di evitare un secondo linguaggio di layout dichiarativo separato come JSX o XML (del quale fanno uso Java, Kotlin, Swift, Objective-C), o di creare interfacce visive separate. 5. Dart possiede una sintassi C-like, il che lo rende di semplice apprendimento. 6
1.5 Material Design (Quantum Paper) Il Material Design (o Quantum Paper) è il Design Language che Google ha sviluppato nel 2014, con lo scopo di fornire al mondo dello sviluppo App un Design universale per gli utenti. L’idea di Material Design si spinge oltre il concetto di design nativo e cerca di superare le barriere definite dalle Human Guideline Interfaces di ogni OS mobile. Il principio di tale design è quello di sfruttare lo spessore del dispositivo per disporre i Materials (oggetti) assegnando a ciascuno una propria profondità attraverso le ombre. Se da un lato il Material Design si pone come obiettivo quello di offrire un Design Language universale, dall’altro Flutter si pone quello di offrire un linguaggio di programmazione universale. Gli scopi di entrambi sono chiaramente molto simili e non è un caso. Infatti Google ha programmaticamente sviluppato Flutter sui pilastri fondamentali del Material Design. 1.6 Flutter vs React Native React Native è un framework JavaScript del 2015 per la scrittura di applicazioni mobile iOS e Android. L’idea alla base di React Native è quella di sfruttare React - la nota libreria per realizzazione di UI (User Interface) in ambito Web - nel mondo mobile. L’avvento di React Native ha letteralmente rivoluzionato il mondo dello sviluppo mobile, grazie all’incredibile intuizione della UI dichiarativa portata avanti da Face- book. Per quanto React Native e Flutter siano simili, esistono radicali differenze circa le filosofie di entrambi i progetti. Mentre React Native spinge molto sulla creazione di App dal "native look and feel" (design coe- rente con la piattaforma utilizzata), Flutter insiste sulla realizzazione di App basate su Material Design (indipendenti dalla piattaforma). Al giorno d’oggi lo scontro tra Flutter e React Native (rispettivamente Google e Facebook) è sempre più acceso, con entrambi i progetti che cercano di portarsi dalla loro parte quanti più mobile developer possibile. Tuttavia la legge del più forte la detta il mercato che - nonostante Flutter abbia recuperato moltissimo terreno (vedi Figura 1.3) - continua a preferire React Native risultando essere la scelta più stabile e diffusa. Tra le App realizzate in React Native vale la pena nominare: Facebook, Instagram, Skype, Testla, Uber. Figura 1.3: Stackoverflow Trends Flutter vs React Native [7] 7
Capitolo 2 State Management 2.1 Introduzione Flutter è uno UI Framework interamente basato sul concetto di stato, il che significa che l’intero “Presenta- tion Layer” dell’applicativo dipende dallo stato corrente. Per enucleare l’importanza del concetto suddetto basta ricordare la differenza tra i due Widget fondamentali: Stateless Widget e Stateful Widget. Tuttavia, Flutter non è l’unico framework a dipendere da questa logica. Infatti, quasi tutti i nuovi Mobile Hybrid Framework – tra cui React Native e Ionic – rispettano questa funzione. Con la nascita di queste nuove tecnologie è nata dunque l’esigenza di creare nuovi Pattern di sviluppo dediti alla gestione della UI in funzione dei cambiamenti di Stato. Questi Pattern sono noti col nome di State Management. Si cercherà dunque di analizzare i principali State Management utilizzabili in Flutter e quali ruoli svolgano nell’architettura complessiva di un’applicazione è quello di studiarne l’impiego nello sviluppo di un’app che funge da Counter. Figura 2.1: Screen App Counter 8
2.2 setState Più che uno State Management, il setState è una funzione che, posta in uno State Widget, ricostruisce inte- ramente la UI. La ricostruzione completa della UI, cioè il rendering dell’intera schermata, non è un metodo conveniente dal punto di vista dell’economicità dei processi coinvolti. Cliccando il “+” del Floating Action Button in Figura 2.1, infatti, sarebbe sufficiente renderizzare soltan- to il testo invece di tutta la pagina. Questa caratteristica lo rende quindi particolarmente svantaggioso per quanto riguarda le performance. L’uso di setState risulta inoltre particolarmente dannoso per la scalabilità dell’applicazione. Più che un ap- proccio di sviluppo vero e proprio, ricorda il noto “Code & Fix” usato agli albori della programmazione. Risulta comunque un metodo valido nel caso di applicazioni realizzate a scopi dimostrativi o didattici. 1 setState (() { 2 _counter++; 3 }) ; 2.3 StatefulBuilder Lo StatefulBuilder è un Widget finalizzato alla risoluzione di problemi relativi alle performance che scatu- riscono dal setState (ma non quelli relativi alla strutturazione di un codice scalabile). La differenza consiste nel fatto che lo StatefulBuilder consente di poter scegliere, tramite una funzione detta “builder”, la parte di UI che necessita di essere ricostruita (in setState viene necessariamente ricostruita tutta la UI dell’applicativo). Un’ulteriore particolarità dello StatefulBuilder è che può essere utilizzato all’interno di uno StatelessWidget. 1 return StatefulBuilder ( 2 builder : (BuildContext context , StateSetter setState ) { 3 return ... 4 }, 5 ); NOTA Questi due approcci (setState e StatefulBuilder) vengono usati raramente, per le motivazioni già enunciate. Tuttavia, nell’adoperare tali metodi, non ci si imbatte nei tipici problemi degli State Management. Questi sono: • comunicazione tra Widget figlio e Widget padre; • comunicazione tra Widget figli. PS: La comunicazione Padre - Figlio non risulta quasi mai complessa. 9
2.4 InheritedWidget & Scoped Model Un InheritedWidget è un Widget in grado di racchiudere un dato accessibile elementarmente da tutti i suoi Widget Figli. Inoltre, un InheritedWidget possiede una funzione nota come “updateShouldNotify”, la quale viene chiamata dai Widget Figli al fine di notificare il Widget padre della circostanza di un avvenuto cam- biamento. In genere si tende a creare un nuovo Widget che eredita le caratteristiche dell’InheritedWidget, in modo da poter definire sia il tipo di dato, sia il comportamento della funzione updateShouldNotify. NOTA Scoped Model è un package di terze parti gestito da Brian Egan (Google Developer). È costruito sulla base di un InheritedWidget che offre un modo leggermente migliore per accedere, aggiornare e mutare lo stato. Permette di passare facilmente un modello di dati da un widget al discendente e ricostruisce anche tutti i "children" che usano il modello quando quest’ultimo viene aggiornato. Per verificare eventuali modifiche, lo Scoped Model utilizza il metodo notifyListeners(); Figura 2.2: Schema di interazione tra Widget Figlio ed InheritedWidget [10] N.B: Il dato racchiuso nell’inheritedWidget viene iniettato nei Widget Figli mediante Dependency Injec- tion1 . 1 La Dependency injection (DI) è un design pattern della Programmazione orientata agli oggetti il cui scopo è quello di semplificare lo sviluppo e migliorare la testabilità di software di grandi dimensioni. 10
2.5 RxDart + BehavoirSubject + GetIt Pur essendo di grande semplicità, questo tipo di approccio riesce ad essere notevolmente efficace. L’idea che ne sta alla base è quella di creare una Classe Wrapper di un BehaviorSubject (un particolare Widget di Flutter). L’uso di un BehaviorSubject presenta i seguenti vantaggi: • esso ha un comportamento analogo allo Stream in Broadcast (può essere ascoltato da qualsiasi Widget dell’applicazione); • rende possibile l’accesso al valore corrente dello Stream da qualsiasi parte del codice; • facilita l’iniezione di un dato in uno Stream. NOTA L’uso degli Stream negli State Management è molto frequente; è quindi fondamentale comprendere cosa essi siano. Per far ciò è utile paragonare lo Stream ad un fiume all’interno del quale scorrono alcuni rami (i dati effettivi). Ogni qual volta cambia un determinato dato, viene immesso un nuovo ramo nel fiume. Per Stream in Broadcast si intende uno Stream osservabile da N Osservatori (il compito del- l’osservatore in Flutter è svolto dallo StreamBuilder) i quali notificano la UI del cambiamento del dato nello stream. Tenendo a mente la Figura 2.1 si avrà una struttura simile alla seguente. 1 class Counter { 2 BehaviorSubject _counter = BehaviorSubject .seeded(0) ; 3 4 Observable get stream => _counter . stream ; 5 int get current => _counter . value ; 6 7 increment () { _counter .add( current +1); } 8 decrement() { _counter .add( current −1); } 9 } Essendo lo stream in Broadcast, basterà porre un StreamBuilder (osservatore dello stream) in un Widget e costruire la UI in relazione allo stato corrente. Risulta allora chiaro che nell’accedere allo Stream si passerà per un’istanza della Classe Counter. Tale istanza dovrà quindi essere unica (nel nostro caso il Counter è unico). Per poter soddisfare questo requisito si possono seguire due diversi percorsi: 1. creare Counter come Singleton; 2. importare la libreria GetIt. Mentre il primo approccio implica i vantaggi e gli svantaggi del Singleton (semplice ma non facilmente testabile), il secondo invece offre la possibilità di iniettare il Counter con Dependency Injection (dipendendo però da una libreria di terze parti). 11
NOTA Se dipendere da una libreria di terze parti non è mai troppo vantaggioso, in Flutter lo è ancora meno. Dati i continui aggiornamenti è molto facile ritrovarsi con una libreria non più adeguata. 2.6 BLoC Il BLoC (Business Logic Component) è lo State Management consigliato da “Google Developers” per la realizzazione di applicazioni in Flutter. È estremamente scalabile ma non semplice quanto l’approccio RxDart + BehaviorSubject. Figura 2.3: Schema generale BLoC [19] La diffusione del BLoC in Flutter è avvenuta in particolar modo grazie alla libreria flutter_bloc ad opera di Felix Angelov (Senior Software Engineer BMW). Essa si struttura in una moltitudine di componenti fonda- mentali, con lo scopo di astrarre alcuni Widget Flutter e di ridurre drasticamente il codice BoilerPlate da scrivere nello sviluppo. I componenti principali sono: • Bloc2 • BlocBuilder • BlocProvider • RepositoryProvider Per comprenderli al meglio, si cercherà di realizzare l’app counter in Figura 2.1 tramite questa architettura. Bloc Il Bloc fornito da tale libreria non è altro che uno Stream che prende in ingresso Eventi e fornisce in uscita un possibile Stato. Tale componente deve inoltre necessariamente implementare la funzione mapEventTo- State, la quale definirà l’intera logica comportamentale. Risulta dunque ovvio che per la configurazione di un Bloc è prima necessario definire: 2 Per BLoC si intende l’astrazione di uno Stream (In Flutter il concetto di BLoC nasce in un secondo momento). 12
1. Eventi; 2. Stati; 3. mapEventToState. Dal punto di vista pratico gli eventi saranno incremento e decremento, mentre lo stato sarà semplicemente costituito dal valore che si sta contando. In termini puramente implementativi si andranno quindi a definire tre file distinti: • counter_events.dart; • counter_states.dart; • counter_bloc.dart. Counter Events Il codice alla base della definizione degli eventi risulta particolarmente semplice: 1 abstract class CounterEvent {} 2 3 class IncrementEvent extends CounterEvent {} 4 class DecrementEvent extends CounterEvent {} La definizione degli eventi sfrutta il principio dell’ereditarietà per declinare un evento generale ed eventi se- condari. Gli eventi secondari (Increment e Decrement) saranno quelli che verranno richiamati dall’utente al click di un pulsante, mentre l’evento generale servirà successivamente nella definizione del Bloc. L’utilizzo di una classe astratta per definire il "CounterEvent" non è necessario, ma è ampiamente raccomandato dalle Guide lines (linee guida) della documentazione per due motivi: • semplificare il riconoscimento dell’evento generale; • prevenire l’errata istanziazione dell’evento generale. Counter States La definizione degli stati, in genere, risulta spesso più complessa di quella degli eventi, in quanto uno stato deve portare con sé informazione (anche nel caso degli eventi può succedere, ma in genere l’evento in sé rappresenta l’informazione da passare). In questo caso infatti è facile notare come il "CounterState" inglobi il valore intero che la view dovrà utilizzare. 1 class CounterState { 2 final int counter ; 3 CounterState ({ this . counter }) : assert ( counter != null ) ; 4 } Il CounterState viene definito come classe wrapper, ma sarebbe bastato usare int counter (il motivo per cui è stata usata una classe wrapper è relativo al fatto che in tutti gli esempi successivi gli stati non saranno mai un tipo atomico e non saranno mai unici). 13
NOTA È importante menzionare che la definizione degli stati richiede quasi sempre l’uso di uno Stato generale da cui ereditano degli Stati secondari (come succedeva con gli eventi). Tuttavia questo risulta un caso particolare, in quanto vi è la necessità di un unico Stato. Counter Bloc Il file alla base del del Bloc è lo stesso che implementa la funzione mapEventToState, il vero e proprio cuore dello State Management. È in tale funzione che emerge il concetto di Programmazione Reattiva3 in Flutter grazie alle keywords yield e async*. Con yield si intende una sottospecie di return, con la sola differenza che "return" determina la fine della funzione, mentre yield immette semplicemente un nuovo dato nel flusso. L’utilizzo della parola chiave async* è fondamentale, in quanto consente l’utilizzo di yield. 1 Stream mapEventToState(CounterEvent event) async∗ { 2 if ( event is IncrementEvent) { 3 yield CounterState ( counter : currentState . counter + 1) ; 4 } else if ( event is DecrementEvent) { 5 yield CounterState ( counter : currentState . counter − 1); 6 } else { 7 yield currentState ; 8 } 9 } L’insieme di questi ingredienti dà vita ad una funzione caratterizzata quasi esclusivamente da "if-else", ma di una potenza disarmante. Inoltre essendo il Bloc considerabile come un’entità Reattiva, esso risulta essere perfettamente schematizzabile tramite State Diagram. Lo State Diagram di un oggetto rappresenta la sequenza di stati, le risposte, le azioni che l’oggetto attra- versa durante la sua vita in risposta agli stimoli ricevuti. Figura 2.4: State Diagram dell’app Counter 3 Per Programmazione Reattiva si intende programmare facendo uso di flussi di dati asincroni. 14
BlocBuilder Il BlocBuilder4 risulta essere un Observer5 a tutti gli effetti. Il suo compito è quello di registrarsi al Flusso di dati (in questo caso al CounterBloc) e notificare la UI in caso di cambiamenti. IncrementEvent DecrementEvent tch pa dis mapEventToState yie BLOC ld +1 BlocBuilder Figura 2.5: Interazione tra utente e BLoC 4 Il BlocBuilder non è nativo in Flutter ma rappresenta un’astrazione del Widget "StreamBuilder". 5 Si intende il noto Design Pattern. 15
BlocProvider Il BlocProvider è un Widget che "provvede" a creare una comunicazione tra View e Bloc. Infatti il Bloc- Builder è un elemento della View e riesce a mettersi in ascolto di un determinato Bloc proprio grazie al BlocProvider. Un’ulteriore caratteristica che lo contraddistingue è la capacità di essere accessibile non solo dal Widget figlio, ma da tutti i Widget sottostanti. Porre un BlocProvider in corrispondenza del primo Widget dell’applicazione rende il Bloc accessibile ovun- que (tale pratica viene in genere evitata, al fine di garantire l’utilizzo di un determinato Bloc, solo dove effettivamente serve). NOTA È interessante notare come queste caratteristiche lo rendano a tutti gli effetti un’inheritedWidget, anch’esso uno degli State Management fondamentali di Flutter. RepositoryProvider Il RepositoryProvider presenta le stesse caratteristiche del BlocProvider, con la sola differenza che mette in comunicazione View e Repository. Questo componente non rientra nell’esempio del Counter, ma viene spesso adoperato per casi ben più com- plessi. Il concetto di Repository in sé fa riferimento al Repository Pattern e descrive generalmente un’astra- zione di un insieme di servizi o classi (sarà visto nel dettaglio successivamente). NOTA Per chiarezza, ho deciso di pubblicare l’esempio in questione su GitHub: https://github.com/egeondundee/counter_app 16
2.7 Redux Redux risulta essere indubbiamente lo State Management de facto di React Native. La sua caratteristica principale è data dall’estrema modularità, pagata al prezzo di tanto codice BoilerPlate. Figura 2.6: Schema generale Redux [12] Dal Diagramma di Redux in Figura 2.6 emergono i seguenti elementi fondamentali: • Action: del tutto equivalente ad un "Evento" in Bloc (coincide con il "click" sul "+" del Counter); • Reducer: ha il compito di "mappare" le Action in futuri Stati che andranno a modificare lo Store; • Store: ha il ruolo di notificare la UI in caso di cambiamenti di Stato. È così immediato notare l’infinità di similitudini esistenti tra Redux e Bloc che si fa fatica a percepirne le differenze, le quali esistono ma risultano quasi nascoste. La differenza sostanziale risiede nelle finalità di ciascuno State Management. Mentre la finalità principale di Bloc è quella di garantire un’eccezionale ge- stione dello Stato Locale, quella di Redux è invece la garanzia di una semplice gestione dello Stato Globale. In ambito realizzativo, in BLoC si vanno sempre a realizzare più Bloc distinti tra loro e indipendenti in grado di gestire singole schermate o singole funzionalità di schermate (dalla schermata A non è concesso l’accesso al Bloc della schermata B), in Redux invece lo scopo è quello di avere uno Store centralizzato facilmente accessibile da ogni componente della UI. Gestione Stato Locale vs Gestione Stato Globale Il dibattito su quale sia la scelta migliore è estremamente aperto ed è spesso ciò che influenza maggiormente la scelta dello State Management. Figura 2.7: Confronto Stato Locale e Globale per gli State Management principali in Flutter [13] 17
Capitolo 3 MovieMentor 3.1 Descrizione MovieMentor è un’app iOS/Android, realizzata come caso di studio per questa tesi, che si propone di consigliare all’utente quali film vedere in TV dopo aver compreso le sue preferenze, attraverso un sistema Tinder-Like. Essa consente inoltre di avere un’overview generale su tutti i Programmi TV (Film/SerieTV) in diretta sui canali televisivi e sui loro dati di interesse (rating, cast, produzione, descrizione). 3.1.1 Use Case Diagram L’insieme dei requisiti espressi nella descrizione possono essere schematizzati come Use Case Diagram. Figura 3.1: Use Case Diagram di MovieMentor 18
I casi d’uso che emergono dal diagramma in Figura 3.1 sono: • Ottieni Programmi TV: si tratta di "raccogliere" quanti più dati è possibile su tutti i Programmi TV trasmessi sui canali televisivi principali, attraverso un Sistema Esterno. • Crea Notifica: è un’operazione che viene realizzata periodicamente dal Sistema Esterno per consi- gliare all’utente i Programmi TV più adatti a lui. Dalla descrizione emerge il fatto che l’app debba essere cross-platform1 , per tale motivo l’intero lato Client sarà realizzato in Flutter. Inoltre va sottolineata la necessità di adoperare NodeJs per i compiti legati al lato Server dal Sistema Esterno. 3.2 Architettura del Progetto L’architettura del progetto è descritta dal Component Diagram in Figura 3.2. Figura 3.2: Component Diagram di MovieMentor 1 Orientata a più sistemi operativi. 19
Il diagramma in Figura 3.1 si suddivide intuitivamente in due sottosistemi principali2 : • External System; • Client Side. Un’ulteriore sottosistema è quello relativo all’invio delle notifiche, dal quale il Sistema Esterno deve ne- cessariamente dipendere (per poter inviare notifiche ad un dispositivo iOS/Android si deve sempre essere autorizzati da un Messaging System). 3.2.1 Struttura Server Side Il back-end, realizzato in NodeJS, è caratterizzato quasi esclusivamente da un Business Layer, il quale ingloba più componenti modulari e coesi. I compiti principali svolti dai singoli componenti sono i seguenti: 1. omologazione API Canali TV sotto un formato unico - Parser; 2. inserimento dei dati in un database non relazionale - DbOperator; 3. algoritmo sulla scelta del Programma TV sulla base delle categorie preferite dall’utente - Parser; 4. invio delle notifiche e memorizzazione - NotificationSender; 5. creazione API - Router. 3.2.2 Struttura Client Side Lo scopo del Front End è quello di fornire all’utente un’applicazione in grado di: 1. visualizzare cronologicamente info sui Programmi TV; 2. ricevere notifiche; 3. impostare filtri per le notifiche; 4. selezionare categorie/generi preferiti di Programmi TV. State Management Lo State Management scelto per la realizzazione del Front End è il BLoC (Business Logic Component). La scelta è stata dettata dai seguenti motivi: • consente la creazione di componenti indipendenti e riusabili; • facile manutenzione: modificare una funzionalità si riduce a modificare una relazione evento-stato; • è l’unico State Management che dà la possibilità di gestire lo Stato Locale. 2 Un sottosistema è un componente che agisce come unità di scomposizione per un sistema più grande. 20
3.3 User Interface Al giorno d’oggi, in particolar modo nel mondo delle app, l’aspetto grafico ha assunto quasi più importanza dell’aspetto funzionale. Il funzionamento dell’app è quasi sempre dato per scontato da parte dell’utente, il quale ha quindi la possibilità di giudicare solo la UI e la UX (User Experience). Per tale motivo la User Interface è stata realizzata con l’obiettivo di integrare tutte le funzionalità richieste in maniera "gradevole". Per riuscire in questo intento si è cercato di realizzare componenti non nativi, prendendo ampiamente spunto dall’HamburgerMenu di Bear (app di note, vincitrice dell’Apple Design Award nel 2017) e dalla nota animazione dell’App Store. Figura 3.3: Confronto Hamburger Menu tra Bear (a sinistra) e MovieMentor (a destra) 3.3.1 Logo Il logo rappresenta Alan Turing (tratto dal film "The Imitation Game"). L’idea che sta dietro questa scelta consiste nell’identificare nel personaggio di Alan Turing i calcoli svolti dal Sistema Esterno, quasi come se egli fosse un mentore per la scelta del film giusto (da qui il nome MovieMentor). Figura 3.4: Logo MovieMentor 21
3.3.2 Schermate App MovieMentor Di seguito, sono mostrate le schermate principali dell’app. Figura 3.5: Overview sui ProgrammiTV Figura 3.6: Impostazioni (a sinistra), visualizzazione notifiche (a destra) 22
3.4 Server Side Questa sezione ha lo scopo di analizzare i compiti svolti dal Sistema Esterno. 3.4.1 Omologazione API Canali TV sotto un formato unico I dati relativi ai programmi televisivi sono ricavati da canali amministrati da società diverse e quindi format- tati in modi totalmente distinti. Questa caratteristica ha fatto sì che fosse necessaria un’operazione di refactoring3 . L’operazione di refac- toring è stata realizzata adoperando il package “node-fetch”, il quale consente di decodificare il JSON ritornato dalle API in un tipo di dato strutturato facilmente gestibile (Dizionario/Map). Tale operazione è suddivisa in N-Parser, dove N rappresenta il numero di formati distinti. Fortunatamente, dato che Rai e Mediaset coprono più di 20 canali televisivi italiani, il progetto si suddivide in soli due parser specifici: 1. rai_parser.js 2. mediaset_parser.js Purtroppo, una volta terminata questa operazione è diventato evidente che molti dati forniti dalle API risul- tavano essere insufficienti. Spesso i campi relativi alla descrizione e all’immagine del programma TV erano nulli; inoltre, non vi era nessuna informazione circa il rating del programma trasmesso. Per tale motivo sono state quindi utilizzate anche le API di TheMovieDB (aggiunta di rating, popolarità, cast, produzione, immagine e descrizione completa). L’idea guida è diventata quella di utilizzare le API di Mediaset e Rai per ottenere le informazioni tem- porali sui canali trasmessi in TV, per poi completarle attraverso quelle di TheMovieDB. Tale idea è stata concretizzata, nell’ambito del progetto, dai componenti themoviedb.js e parser.js. Nello specifico, il compito del file themoviedb.js è quello di fornire le funzioni finalizzate all’ottenimento del programma TV a partire dal titolo, mentre quello del file parser.js è di effettuare un merge tra i dati ottenuti dalle API dei canali televisivi e quelli ottenuti da TMDB. In tal modo per la maggior parte dei pro- grammi TV è stato possibile ottenere molta più informazione al prezzo di un estremo calo delle performance (dovendo effettuare un “merge” vero e proprio tra più dati distinti). Semplificando molto, prima di introdurre TMDB il tempo di risposta di un’API era pari alla somma del tem- po di risposta Rai e Mediaset, con l’aggiunta del tempo necessario per il refactoring. Tale tempo di risposta non andava quasi mai oltre i 700ms. Con l’aggiunta dei dati di TMDB, si è resa necessaria una chiamata a TheMovieDB per ogni Programma TV, portando il tempo di risposta complessivo ad un valore assolutamente inaccettabile. 3 Con Refactoring si vuole intendere l’operazione di omologazione dei dati sotto un formato unico. In questo caso, si intende dunque la ricostruzione di una struttura uguale sia per i dati della Rai, che quelli di Mediaset. 23
Esempio di JSON Risultante dall’operazione di Omologazione . 1 { 2 "timePublished": "22:33", 3 "title": "Big Bang Theory", 4 "type": "TV SERIES", 5 "description": "Guest star: Charlie Sheen. Raj e’ esaltato dal fatto che la rivista \"People\" lo ha inserito in una classifica dei trenta giovani scienziati piu’ promettenti.\nVISIONE ADATTA A TUTTI.\nQUESTO PROGRAMMA E’ SOTTOTITOLATO ALLA PAGINA 777DEL MEDIAVIDEO.", 6 "duration": "00:20:00", 7 "channel": "Italia 2", 8 "day": "09-01-2019", 9 "mainCategories": [ 10 "Commedia" 11 ], 12 "subCategories": null, 13 "tmdb_info_is_ok": 1, 14 "tmdb_info": { 15 "overview": "Pasadena, California. Quattro giovani scienziati di diversi campi: il fisico sperimentale Leonard Hofstadter, il fisico teorico Sheldon Cooper, l’astrofisico Rajesh Koothrappali e l’ingegnere aerospaziale Howard Wolowitz lavorano insieme al California Institute of Technology. Questi fanno amicizia con Penny, una bella ragazza di provincia col sogno di diventare attrice, venuta a vivere nell’appartamento di fronte a quello condiviso da Leonard e Sheldon.", 16 "popularity": 58.358, 17 "release_date": null, 18 "img": "http://image.tmdb.org/t/p/w500/ooBGRQBdbGzBxAVfExiO8r7kloA.jpg", 19 "rating": 6.8, 20 "genres": [ 21 "Commedia" 22 ], 23 "cast": [...], 24 "crew": [...] 25 }, 26 }, 24
3.4.2 Inserimento dati nel Database Per evitare le complicazioni derivanti dall’evoluzione delle API si è deciso di creare un database all’interno del quale immettere periodicamente i nuovi dati. In tal modo, il nuovo tempo di risposta dipende esclusivamente dal tempo di risposta della query nel DB. Come DBMS4 è stato scelto MongoDB. 3.4.3 MongoDB MongoDB (da "humongous", enorme) è un DBMS non relazionale, orientato ai documenti. Classificato come un database di tipo NoSQL , MongoDB si allontana dalla struttura tradizionale dei database relazio- nali basata su tabelle in favore di documenti in stile JSON con schema dinamico, rendendo l’integrazione di dati di alcuni tipi di applicazioni più facile e veloce. Rilasciato sotto una combinazione della GNU Affero General Public License e dell’Apache License, Mon- goDB è un software libero e open source. Perché MongoDB? La scelta di MongoDB è stata dettata dalla constatazione della sua semplicità e della sua ottima integrazione con NodeJS. Inoltre, va specificato che il modo in cui il database è stato utilizzato in questo progetto non è quello di un database tradizionale, bensì quello di Buffer di JSON (finalizzato esclusivamente alla minimizzazione dei tempi di risposta). Perché non un Database Relazionale? L’unico motivo per cui è stato adoperato un Database nel progetto è la necessità di “custodire i dati”, con lo scopo di minimizzare il tempo di risposta delle chiamate. Usare un database relazionale non sarebbe stato sbagliato, ma avrebbe comportato lo sforzo della creazione di tabelle e relazioni in ultima analisi inutili, considerato che i dati che servono sono esclusivamente quelli inseriti. 4 In informatica, un Database Management System, abbreviato in DBMS o Sistema di gestione di basi di dati, è un sistema software progettato per consentire la creazione, la manipolazione e l’interrogazione efficiente di database, per questo detto anche "gestore o motore del database". 25
3.4.4 Algoritmo sulla scelta del Film/SerieTv L’algoritmo relativo alla scelta del Film/SerieTV sulla base delle categorie preferite dell’utente è stata forse una delle parti più stimolanti ed impegnative del processo di creazione di questo progetto. Per comprendere a fondo la logica utilizzata è necessario considerare un ipotetico caso d’uso. Supponiamo che un generico utente sia appassionato di film Crime e Biografici, supponiamo inoltre che durante la setti- mana venga trasmesso in TV il film “The Wolf of Wall Street”. Consideriamo inoltre i dati delle categorie relative al film preso in esempio. Figura 3.7: Confronto dati generi/categorie "The Wolf of Wall Street" Sono stati scelti i dati di Wikipedia in quanto molto simili a quelli riproposti dai canali TV. Supponendo di effettuare un “merge” tra i dati di Wikipedia e quelli di TMDB, le categorie di “The Wolf of Wall Street” risultanti sarebbero le seguenti: Crime, Drammatico, Commedia, Biografico. A primo impatto, al fine di ottenere il film giusto per l’utente, si potrebbe pensare di “scorrere” i dati presenti nel DB e restituire il film che possiede il rating superiore e che presenta al contempo anche le cate- gorie preferite dall’utente. Con molta probabilità quest’algoritmo restituirebbe "The Wolf of Wall Street” come film consigliato per l’u- tente, nonostante questo sia solo marginalmente un film del genere “Biografico e Crime”. 26
Come risolvere quindi questo problema? L’errore principale sta nel “merge” effettuato tra i dati. Affermando che “The Wolf of Wall Street” è un film che rientra nei generi “Crime, Drammatico, Commedia e Biografico”, stiamo affermando che tutte le categorie descrivono in egual misura il film (in termini percentuali sono tutte al 25% appartenenti al film). Dato che questo tipo di approccio risulta evidentemente fallimentare, è necessario suddividere l’algoritmo in due distinte fasi: 1. “merging pesato” delle categorie; 2. selezione del film/SerieTV. Merging pesato delle categorie L’idea alla base di questa fase cerca di attribuire determinati pesi ad ogni film, sulla base dei dati ottenuti. Per chiarezza ripropongo i dati precedentemente esposti: • Wikipedia: commedia, drammatico, biografico; • TMDB: crime, drammatico, commedia. Dal punto di vista probabilistico si può ipotizzare che le categorie “Commedia” e “Drammatico” hanno si- curamente un peso superiore alle altre, in quanto sia Wikipedia che TMDB le ritornano, mentre le rimanenti possono essere supposte come categorie secondarie. In particolare ciò che succede è che ogni volta che due categorie risultano identiche nella fase di “merge”, i pesi vengono sommati. All’inizio quindi si avrà: 1 { TMDB_Genres: {Crime: 1.0, Drammatico: 1.0, Commedia: 1.0}} 2 { Wikipedia_Genres: {Commedia: 1.0, Drammatico: 1.0, Biografico: 1.0}} 3 4 { totalGenresWithScore: { 5 Crime: 1.0, 6 Drammatico: 2.0, 7 Commedia: 2.0, 8 Biografico: 1.0 9 } 10 } Normalizzando i dati ottenuti da totalGenresWithScore a 1.0 si ottiene: 1 { totalGenresWithScore: { 2 Crime: 0.5, 3 Drammatico: 1.0, 4 Commedia: 1.0, 5 Biografico: 0.5 6 } 7 } 27
Sulla base dei dati ottenuti si cerca di effettuare la selezione. Riporto alcuni esempi (i dati non sono normalizzati a 1.0): NOTA Chiaramente è necessaria un’operazione di refactoring anche sui generi dei film. Infatti, i dati otte- nuti da un merge tra Rai, Mediaset e TMDB portano ad ottenere più di 100 categorie diverse, per cui si è cercato di omologarle fra loro, al fine di ridurle a circa 20. L’omologazione ha portato ad ulteriori sfumature. Cosa si intende per omologazione in questo caso? Ecco alcuni esempi: 1 {in_costume: {Storico: 1.0}}, 2 {spionaggio: {Crime: 0.5, Azione: 0.5}}, 3 {sci-fi: {Fantascienza: 1.0}}, 4 {sovrannaturale: {Horror: 0.5, Fantasy: 0.5}} L’operazione di omologazione è stata realizzata tramite espressioni regolari. Le categorie ottenute alla fine del processo sono: Commedia, Parodia, Drammatico, Azione, Avventura, Thriller, Crime, Giallo, Horror, Romantico, Storico, Fantasy, Fantascienza, Animazione, Musica, Famiglia, Western, Natura, Documentario, Sportivo. 28
Selezione del Film/SerieTV L’operazione di selezione è piuttosto semplice, trattandosi di una ricerca lineare. Innanzitutto va specificato che dal lato applicativo i dati ottenuti sulle categorie preferite saranno inviati in maniera radicalmente diversa rispetto a quanto avveniva prima (nell’esempio introduttivo). Se prima l’utente generico inviava le categorie come “Crime e Biografico” (in un’array), adesso le invia tramite un dizionario ed in forma pesata (esempio: Crime: 1.0, Biografico: 0.5). Questo fa si che l’operazione di selezione del film si riduca ad essere una ricerca del minor errore qua- dratico medio. xi )2 ∑ni=1 (xi −ˆ MSE = n In termini semplici, si scorreranno tutti programmi TV del giorno dd/mm/yyyy in tal modo: (genres[Crime]−programsi [Crime])2 +(genres[Biogra f ico]−programsi [Biogra f ico])2 MSE = genres.length Il programma TV con il minor MSE sarà quello che avrà le categorie più simili a quelle desiderate dall’utente (in termini pesati). Per garantire anche la qualità del programma TV, è necessario che il rating di questo sia superiore a 7.5 (il massimo è 10). L’utente dall’app avrà la possibilità di aggiungere ulteriori filtri (fascia oraria, solo Film, solo Serie TV). NOTA N.B: Quest’algoritmo è basato su una serie di assunzioni che non necessariamente risultano ve- rificate, anche se sperimentalmente hanno fornito buoni risultati. E’ inutile dire che riuscire ad ottenere sempre il programma TV più adatto all’utente richiederebbe una logica ben più complessa. 29
3.4.5 Invio delle notifiche e memorizzazione Questa fase non risulta esclusivamente relativa a nodejs ma è frutto di una “cooperazione” tra back-end e front-end. Innanzitutto il sistema adoperato per l’invio delle notifiche è stato Firebase Cloud Messaging. Firebase Cloud Messaging Firebase Cloud Messaging (FCM), precedentemente noto come Google Cloud Messaging (GCM), è una soluzione cloud multipiattaforma per messaggi e notifiche per Android, iOS e applicazioni web, che at- tualmente può essere utilizzata gratuitamente. Il servizio è fornito da Firebase, una società controllata da Google. FCM consente agli sviluppatori di software di inviare notifiche push per le loro applicazioni agli utenti finali attraverso delle API. La scelta è ricaduta su FCM essendo il servizio gratuito, ben documentato e ricco di librerie integrabili su node. La libreria con la quale è stato integrato Firebase Cloud Messaging a node è “node-fcm”. 3.4.6 node-fcm La libreria node-fcm consente di inviare notifiche push ad un mittente identificato tramite un token (univo- co). In particolare, il token identifica il dispositivo e può essere ottenuto esclusivamente dall’applicativo. Affinché possa essere inviata una notifica ad un determinato token, devono prima essere verificate due fasi fondamentali. 1. invio token via app; 2. memorizzazione token in DB. Questo fa si che in MongoDB venga creato un nuovo documento (chiamato users) all’interno del quale sono presenti tutti i token registrati. Essendo i token veri e propri identificativi ed essendo sicuramente unici, è possibile memorizzare anche le categorie preferite dall’utente per ogni token (questo consente la memorizzazione di dati per utente, senza necessaria registrazione). Una volta effettuate ambedue le fasi è possibile inviare la notifica push attraverso node-fcm in un messaggio formattato come segue: 1 var message = { 2 to : token , 3 collapse_key : ’type_a’ , 4 5 notification : { 6 title : programMessage. title , 7 body: programMessage.channel + "ore : " + programMessage.timePublished, 8 click_action : ’FLUTTER_NOTIFICATION_CLICK’ 9 }, 10 }; 30
3.4.7 Creazione API L’ultima fase svolta da node è chiaramente la creazione delle API. La libreria utilizzata per la realizzazione delle API è express.js (la quale consente la creazione di Router5 ) in quanto di una semplicità disarmante e ben documentata. Le API fondamentali attualmente realizzate sono: • API con info su Programmi TV trasmessi per ogni canale; • API con info sui Programmi TV trasmessi per orario; • API con info sui Programmi TV trasmessi per giorno dd-mm-yyyy; • API con info sugli utenti (categorie preferite e token); • API con info circa le categorie utilizzate. Questa fase è risultata indubbiamente la più semplice di tutto il back-end in NodeJS, ma al contempo risulta essere la più importante. È proprio grazie alle seguenti API che è possibile stabilire una relazione tra Front- End in Flutter e back-end in Node, consentendo quindi l’utilizzo dell’approccio CLI (Call Level Interface). Figura 3.8: Schema relativo all’approccio CLI 5 Un Express.Router è un Handler di Web Routes. 31
3.5 MovieMentor - Front End Nella realizzazione dell’app MovieMentor è stato fatto un larghissimo utilizzo di Bloc. Questa strutturazio- ne ha consentito un’eccezionale gestione dello stato locale ed una forte indipendenza di ogni componente fondamentale. Componenti come il gestore delle notifiche, il loader dei programmi, l’AnimationController dell’HamburgerMenu risultano essere facilmente riutilizzabili. Tuttavia, seppur i Bloc adoperati risultano essere sostanzialmente diversi tra loro (per compiti e ruoli), nello sviluppo si è notato che sommariamente sono tutti assimilabili a due categorie fondamentali: • Bloc basati su Servizi Esterni; • Bloc non basati su Servizi Esterni. 3.5.1 Bloc basati su Servizi Esterni Questa tipologia di Bloc è sicuramente molto più complessa strutturalmente della seconda, in quanto implica l’utilizzo di molti più livelli. MovieMentor Remote DB API Service UI BLoC Repository MovieMentor DAO Local DB Figura 3.9: Schema Bloc basato su Servizi Esterni I principali Bloc che adoperano questa struttura sono: 1. database_bloc: Bloc impiegato nella gestione del DB Locale (è stato usato SQLite); 2. programs_bloc: Bloc impiegato per il Loading dei Programmi nella schermata "Programmi TV"; 3. notification_bloc: Bloc impiegato per la gestione delle notifiche. Dallo schema proposto sopra, saltano subito all’occhio i concetti di Repository, Service, DAO. Cerchiamo di comprenderli a fondo. Service Con il termine Service o Service Provider, si intende tipicamente un insieme di funzioni che effettuano richieste http nei confronti di servizi esterni. In generale tali funzioni tendono ad essere asincrone. 32
DAO In informatica, nell’ambito della programmazione Web, il DAO (Data Access Object) è un pattern archi- tetturale per la gestione della persistenza: si tratta fondamentalmente di una classe con relativi metodi che rappresenta un’entità tabellare di un RDBMS, usata principalmente in applicazioni web sia di tipo Java EE sia di tipo EJB, per stratificare e isolare l’accesso ad una tabella tramite query (poste all’interno dei metodi della classe) ovvero al data layer da parte della business logic creando un maggiore livello di astrazione ed una più facile manutenibilità. I metodi del DAO con le rispettive query interne verranno così richiamati dalle classi della business logic. Nel caso di MovieMentor le classi DAO sono state create per poter effettuare le operazioni CRUD6 sul Database Locale (in SQLite). In particolare sono state create le classi DAO relative ai Programmi TV ed alle loro categorie. Repository Il concetto di Repository è di capitale importanza in moltissimi pattern (Model View Presenter, Model View- View Model, Redux, Bloc ecc...). La sua utilità nel Bloc State Management è quella di realizzare un’ulteriore livello di astrazione tra Data-Layer e Bloc, al fine di “mascherare” al Bloc la sorgente dati. Il Bloc non è interessato a sapere da dove provengano i dati e di certo, non ha il compito di richiamare le funzioni delle classi DAO o dei ServiceProvider. NOTA Un esempio lampante di come funzioni la tipologia “Bloc basati su Servizi Esterni” è dato indub- biamente dal Bloc “programs_bloc” il quale, come citato prima, è impiegato per il Loading dei Programmi sulla schermata Programmi TV. programs_bloc Gli step per la definizione di tale Bloc sono: 1. definizione di un ServiceProvider (in questo caso non servono DAO); 2. definizione di una Repository; 3. definizione Eventi e Stati + mapEventToState. ServiceProvider In questo particolare caso il ServiceProvider è dato da una classe denominata “Pro- gramApiProvider” con la sola funzione fetchPrograms (funzione dedita all’ottenimento dei programmiTV tramite richiesta http post al database remoto MongoDB). 6 Create, Retrieve, Update, Delete. 33
Repository La definizione della Repository è veramente elementare in quanto si tratta semplicemente di inglobare tutte le funzionalità dei servizi su cui si fa affidamento. Per comprenderne la semplicità basta osservare il codice. 1 class ProgramRepository { 2 ProgramApiProvider _programApiProvider = ProgramApiProvider(); 3 Future fetchPrograms(int from, int limit ) => _programApiProvider.fetchPrograms(from, limit ) ; 4 } La parola chiave Future
Puoi anche leggere