Sviluppare app mobile con React Native
Introduzione
AGGIORNAMENTO 22/10/2020: vuoi metterti al passo con le ultime feature di React 16? Guarda il webinar React: pratiche per scrivere un’applicazione “moderna” !
React Native è un framework opensource realizzato da Facebook che consente di sviluppare applicazioni cross-platform mobile (iOS e Android) in Javascript utilizzando il paradigma di React.
Spesso gli sviluppatori che si affacciano al mobile (ma anche quelli con esperienza nel settore) condividono lo stesso timore di fondo: riuscirò a dominare la complessità di un mondo in continua evoluzione? Posso applicare le mie conoscenze pregresse in ambito web per lo sviluppo mobile? Quali linguaggi serve sapere e quanto tempo dovrò spendere per imparare a lavorare con gli ecosistemi iOS e/o Android?
Sono tutte domande che possono scoraggiare sul nascere, a cui React Native cerca di dare risposte semplici. Il necessario per partire è:
- una conoscenza base del framework React, che si può acquisire in tempi brevi (parliamo di ore)
- una discreta dimestichezza con il Javascript (preferibilmente ES6)
- Xcode installato (se si vuole sviluppare su iOS) e/o framework Android installato
- una conoscenza di base dell’ecosistema target (vedi paragrafo successivo)
Ad altissimo livello, la differenza tra React e React Native consiste essenzialmente nel “target” dell’applicazione:
- il DOM nel caso di React
- la UI nativa della piattaforma corrente nel caso di React Native
Oltre a ridurre notevolmente l’overhead richiesto al programmatore in termini di conoscenze per lo sviluppo mobile, React Native porta con sè il vantaggio di un framework cross-platform: il codice Javascript che scriviamo “gira” su iOS e Android, per cui il porting di un’applicazione tra i due ecosistemi è “quasi” trasparente.
Il “quasi” (praticamente obbligatorio nel mondo mobile) si riferisce all’eventualità che l’app utilizzi aspetti “nativi” diversi nei due ecosistemi, che pertanto devono essere gestiti in maniera ad-hoc nel codice.
Il debugging di un’app React Native non potrebbe essere migliore: chi proviene dal mondo React sa bene quanto sia comodo vedere la propria applicazione aggiornata semplicemente salvando il codice su cui si sta lavorando; questo è possibile anche in React Native senza dover ricompilare nessun progetto Xcode o Android. L’integrazione con i developer tools di chrome è inoltre supportata by default.
Nonostante il framework sia cross-platform, React Native è fortemente legato alla piattaforma nativa su cui sta “target”. Con React Native non si sviluppa infatti una “web app mobile” a-la phonegap ma una vera e propria app nativa, utilizzando gli stessi blocchi grafici base di un’app nativa Android o iOS.
Nel concreto, lo sviluppatore scriverà componenti React in Javascript/JSX che vengono “mappati” nel loro equivalente nativo. Per esempio, il rendering del componente ActivityIndicator
di React Native utilizza “sotto” una istanza della classe Java/Objective-C di riferimento dell’sdk nativo, cioè il classico spinner Android piuttosto che la “rotellina” di iOS. Lato Javascript React Native espone, per ogni componente, un insieme di proprietà comuni a tutte le implementazioni native, ad esempio la proprietà booleana “animating” nel caso dell’activity indicator.
Il meccanismo di mapping su entità native, inoltre, non riguarda i soli componenti grafici ma anche le api di sistema, che sono esposte al Javascript in maniera analoga.
Infine l’utente può estendere il framework implementando i propri binding nativi (per esempio per sfruttare lato Javascript una funzionalità nativa non ancora integrata ufficialmente in React Native).
Questa flessibilità consente di avere applicazioni ibride, in cui una parte viene sviluppata nativamente e un’altra in React Native. Per chi è abituato al nativo questa possibilità potrebbe essere determinante per prendere in considerazione una transizione a React Native.
Integrazione con l’ecosistema – Vantaggi e svantaggi
React Native non esime il programmatore da una conoscenza, seppure di base, dell’ecosistema target (iOS e/o Android): aver chiaro il risultato finale in termini di grafica e interazione è un prerequisito che diventa sempre più importante nella misura in cui si cerca di implementare un’app simile all’equivalente nativo.
Come descritto in precedenza, infatti, lo sviluppatore delega al framework il “mapping” dei componenti React Native nei corrispondenti nativi, ma non c’è nessun aiuto da parte di React Native riguardo a come questi componenti dovrebbero essere accostati od organizzati in modo da realizzare un’interfaccia nativa coerente.
Insomma, l’effetto “nativo” in React Native non è a costo zero: bisogna leggersi le guideline di iOS piuttosto che Material Design proprio come si farebbe per un’app nativa.
Dato che la copertura dei componenti nativi Android e iOS da parte di React Native è parziale (né rientra fra gli obiettivi del framework coprire la totalità dell’sdk), è possibile che alcune funzionalità previste per la propria applicazione non siano supportate ufficialmente.
In questi casi lo sviluppatore deve essere di volta in volta in grado di capire:
- se esistono pacchetti di terze parti su github che risolvono il problema
- se è necessario wrappare la funzionalità di interesse in maniera ad-hoc
cosa che può non essere immediata.
Se il requisito principale di un’app è quello di avere esattamente stessa grafica e stessa interazione su Android e iOS, invece, tutti gli svantaggi descritti in precedenza si annullano e React Native diventa il candidato ideale per lo sviluppo. In definitiva, quanto più l’app astrae da un ecosistema specifico tanto più conveniente è l’utilizzo di React Native.
Al di là dell’effetto “nativo” o non di un’app, chi sviluppa in React Native deve comunque sapersi muovere su Xcode e/o Android Studio (o Android command-line). Fortunatamente non è necessario saper creare un progetto Android o Xcode da zero: React Native fornisce infatti un pacchetto chiamato “react-native-cli” contenente alcune utility:
- per la creazione di un progetto (
react-native init
) - per l’esecuzione dell’app (
react-native run-android
oreact-native run-ios
) - per il linking di dipendenze native (
react-native link
).
ed altre ancora.
L’embedding delle risorse nel pacchetto di installazione (apk per Android o ipa per iOS) è automatico per quelle referenziate direttamente dal Javascript con l’istruzione “require” (al momento “require” supporta solo immagini, file json e file Javascript), mentre va fatto manualmente per le altre risorse.
In altre parole, se l’app utilizza un font custom allora va aggiunto come risorsa al progetto Xcode e Android manualmente, secondo le modalità previste per un’app nativa (la documentazione React Native in merito a questi punti è in genere dettagliata).
Anche il testing di un’app (ad esempio con TestFlight), così come la creazione della scheda del Playstore piuttosto che di iTunes Connect non ha niente a che vedere con React Native: sono tutte attività da svolgersi secondo le modalità previste dall’ecosistema.
Interazione Nativa
Develer ha all’attivo diversi progetti realizzati in React Native, tra cui una app sviluppata per conto di LILT (Lega Italiana Lotta ai Tumori) per la prevenzione del tumore al seno.
L’app è opensource su github ed è stata sviluppata sia per Android sia per iOS, per smartphone e tablet.
LILT è il classico esempio di applicazione mobile con contenuto prevalentemente statico, in cui l’obiettivo era fornire un’esperienza il più possibile “nativa” utilizzando React Native.
Laddove possibile abbiamo perciò utilizzato componenti ufficiali e standard, ma in alcuni casi questo non è bastato.
Platform-aware tab widget
Nella pagina strutture dell’app, ad esempio, compare un tab widget che distingue i vari tipi di struttura (Centri Seno, iSPO, …) diverso tra Android e iOS. Il design dell’app richiedeva infatti una variante del segmented control di sistema per iOS e il classico tab widget per Android (ad esempio quello in Whatsapp) che però nativamente consente anche lo swipe tra le tab. Abbiamo perciò una differenza estetica ma anche in termini di user interaction.
A parte il segmented control custom su iOS non esiste un componente standard React Native per il widget Android, quindi abbiamo cercato una soluzione alternativa su github.
Fortunatamente la community React Native è molto vasta e attiva, e il più delle volte sono presenti pacchetti di terze parti che forniscono i “pezzi mancanti” (una lista curata di pacchetti React Native non ufficiali ma di buona qualità si trova su github).
Alla fine abbiamo optato per il pacchetto react-native-scrollable-tab-view, che fornisce out-of-the-box tutte le funzionalità che ci servivano.
Una volta individuati i componenti da usare, gestire il rendering diverso sulle due piattaforme è molto facile in React Native, che mette a disposizione due meccanismi base:
- un pacchetto
Platform
che consente di discriminare la piattaforma con un if (e.g.Platform.os === 'android'
).
Questo è stato sufficiente nel caso del nostro tab widget. - un riconoscimento automatico del componente da usare sulla base del nome del file. Supponendo che il componente da utilizzare sia “C”, possiamo creare due file “C.android.js” e “C.ios.js”: React Native userà il file giusto a seconda della piattaforma corrente. La pagina di glossario nell’app utilizza questo approccio.
Wrapping di feature native iOS per la pagina di glossario
Proprio a riguardo dell’implementazione della pagina del glossario abbiamo incontrato un altro scoglio. Questa pagina presenta una search bar per la ricerca delle singole voci di glossario e una lista di voci con sezioni. Su iOS, inoltre, è presente una barra a destra della lista contenente le lettere iniziali delle sezioni per scrollare velocemente alla voce di interesse (tipo l’app dei contatti del telefono).
Limitando il campo di discussione al solo iOS, non esistono componenti ufficiali né per la barra delle lettere a fianco della lista né per la barra di ricerca. Anche qui abbiamo usato un pacchetto di terze parti (react-native-search-bar) per la barra di ricerca, ma la lista di voci ha richiesto un po’ di lavoro in più.
Il componente nativo iOS da usare per la lista di voci è UITableView (molto complesso e abbastanza centrale nell’sdk di iOS), le cui funzionalità sono wrappate solo in parte in React Native da un pacchetto non ufficiale, ma con abbastanza “stelle” su github.
Dato che il supporto React Native alla barra delle lettere non era stato ancora implementato nel pacchetto, abbiamo forkato il repo github e aggiunto questa funzionalità; infine, abbiamo usato il nostro fork come dipendenza nel “package.json” (il metodo ufficiale con cui si gestiscono le dipendenze nei progetti React, e quindi React Native).
Cerchi sviluppatori React Native?
Scopri i servizi di sviluppo Develer
Gestione di diverse risoluzioni
L’app LILT è stata pubblicata sia per smartphone sia per tablet, ma riutilizzando lo stesso design grafico su entrambi i tipi di device.
In linea di principio è sempre preferibile ripensare l’interfaccia grafica di un’app nel momento in cui si decide di pubblicarla anche per tablet, ad esempio per sfruttare meglio la maggiore superficie disponibile. Per motivi di budget e/o scadenze a volte non è possibile realizzare una nuova grafica per tablet e si opta per un riadattamento della grafica smartphone. Questo è ciò che è accaduto con l’app LILT.
Per gestire risoluzioni così diverse tra loro non c’è una regola generale. Nel nostro caso avevamo prevalentemente pagine di contenuto statico che si adattavano bene sia ai vari smartphone sia ai tablet, quindi ci siamo concentrati sulla home page, quella più problematica dal punto di vista della resa grafica perché:
- il requisito era di mostrare interamente il contenuto sui vari device senza dover scrollare
- la pagina si sviluppa in verticale e su smartphone non particolarmente alti (tipo iPhone SE) il contenuto non era interamente visibile
- data la scarsità di voci di menù, su tablet avevamo il problema opposto, ovvero molto spazio bianco inutilizzato al di sotto del contenuto
Sfruttando il fatto che l’app è bloccata in portrait mode, abbiamo deciso di suddividere i vari device in quattro “fasce di altezza” a seconda del numero di pixel logici (controllando prima le risoluzioni presenti sul mercato):
- sopra 1000 dip di altezza
- sopra 700 dip di altezza
- sopra 600 dip di altezza
- altro
e aggiustare ad-hoc il layout flexbox di React Native della home page in base alla fascia del device corrente.
Nel concreto abbiamo utilizzato il pacchetto Dimensions
di React Native e implementato una semplice funzione Javascript per scegliere il valore giusto di altezza di un elemento generico:
const dim = Dimensions.get('window');
const computeHeight = (over1000, over700, over600, other) => {
if (dim.height >= 1000)
return over1000;
if (dim.height >= 700)
return over700;
if (dim.height >= 600)
return over600;
return other;
};
Nei fogli di stile invochiamo la funzione con i valori ad-hoc di altezza che abbiamo trovato empiricamente:
home: {
paddingTop: ios ? 0 : 20,
menuHeight: computeHeight(310, 250, 220, 190),
belowMenuHeight: 100,
logoImageHeight: computeHeight(90, 70, 55, 50),
logoImage: require('../images/logo_home.png'),
...
}
Questo semplice trucco è stato sufficiente per raggiungere l’obiettivo.
Chiamate, apertura di link e mappe
L’app LILT ha una sezione “strutture” contenente numeri di telefono, email e indirizzi utili per gli utenti che hanno bisogno di informazioni sui centri di senologia nell’area fiorentina.
Le info sono “cliccabili” e ciò che accade rispecchia il comportamento nativo di un’app:
- Se si clicca su un numero di telefono parte una chiamata verso quel numero
- Se si clicca su un indirizzo email si apre il client nativo di posta con i campi precompilati, pronto per l’invio di una mail
- Se si clicca su un indirizzo si apre l’applicazione di mappe nativa, che piazza un marker sull’indirizzo specificato
React Native gestisce questo genere di interazioni con il solo modulo Linking
, che consente di “aprire” url generici con la funzione Javascript openURL
.
Per esempio:
- l’url relativo ad una chiamata verso il numero 055 3984627 è tel:0553984627. Aprire l’url fa iniziare la chiamata con il dialer di sistema.
- l’url per inviare una mail potrebbe essere mailto:info@develer.com
- l’url per l’apertura di una mappa su Android è http://maps.google.com/?q=…
- l’url per l’apertura di una mappa su iOS è http://maps.apple.com/?q=…
Lato React Native è sufficiente una semplice funzione di utilità per gestire tutti questi casi:
const openURL = (url) => {
return Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
return Linking.openURL(url);
}
else {
console.warn('Cannot open URL: ' + url);
}
})
.catch((e) => console.warn(e));
};
Conclusione
React Native è un framework che semplifica decisamente la vita a chi si affaccia al mobile, ed è appetibile anche agli esperti del settore come tecnologia di sviluppo cross-platform alternativa al nativo o ad altri framework come phonegap.
Consente di gestire facilmente la complessità di un progetto mobile e di portare con tempi mediamente molto contenuti un’app da una piattaforma all’altra. Dà inoltre la flessibilità di poter intervenire a livello nativo ove necessario.
La conoscenza dell’ecosistema target è comunque richiesta, e in modo tanto più profondo quanto più si cerca di ottenere una app che rispecchia il suo equivalente nativo. In questi casi React Native rischia di essere, a causa della relativa “giovinezza” del framework e della community, un ostacolo piuttosto che un aiuto.
Con una community fortemente attiva e una codebase opensource, React Native è sicuramente una tecnologia da provare per lo sviluppo mobile.