Sviluppare app mobile con React Native

Autore: Alessandro Giannini

Introduzione

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 è:

Ad altissimo livello, la differenza tra React e React Native consiste essenzialmente nel “target” dell’applicazione:

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:

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:

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:

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).

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é:

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):

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:

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:

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.