PNL full-stack con React: Ionic vs Cordova vs React Native

Pubblicato: 2022-03-11

Nei circa 15 anni trascorsi da quando Apple ha rilasciato il primo iPhone, il panorama dello sviluppo del software è cambiato radicalmente. Man mano che gli smartphone ottengono un'adozione diffusa e continuano a crescere in capacità uniche, gli utenti preferiscono sempre più accedere ai servizi software tramite dispositivi mobili piuttosto che desktop o laptop. Gli smartphone offrono funzionalità come la geolocalizzazione, l'autenticazione biometrica e il rilevamento del movimento, molte delle quali solo ora le piattaforme desktop stanno iniziando a copiare. In alcuni dati demografici, uno smartphone o un dispositivo mobile simile è il mezzo principale di consumo del software, aggirando del tutto i computer.

Le aziende hanno notato questo cambiamento e lo hanno rafforzato in modi importanti. Le app mobili non sono più un ripensamento. Applicazioni che vanno da Robinhood, una società di intermediazione finanziaria, a Instagram, una società di social media, a Uber, una società di servizi di assistenza, stanno adottando una strategia di sviluppo mobile-first. Se esiste un'applicazione desktop, viene spesso offerta come complemento all'app mobile, piuttosto che come obiettivo principale.

Per gli sviluppatori full-stack, adattarsi a queste tendenze mutevoli è fondamentale. Fortunatamente, sono disponibili molte tecnologie mature e ben supportate per aiutare gli sviluppatori Web ad applicare le proprie competenze allo sviluppo mobile. Oggi esploreremo tre di queste tecnologie: Cordova, Ionic e React Native. Utilizzeremo React.js, uno dei framework più popolari per lo sviluppo web front-end, come nostra tecnologia di sviluppo principale. Mentre ci concentreremo sullo sviluppo di un'applicazione per iPhone, queste sono tecnologie multipiattaforma e saranno cross-compilabili con la piattaforma Android.

Cosa costruiremo oggi

Costruiremo un'applicazione che utilizza Natural Language Processing (NLP) per elaborare e curare i feed di Twitter. L'applicazione consentirà all'utente di selezionare una serie di handle di Twitter, estrarre gli aggiornamenti più recenti utilizzando un'API di Twitter e classificare i tweet in base al sentimento e all'argomento. L'utente sarà quindi in grado di visualizzare i tweet in base al sentimento o all'argomento.

Estremità posteriore

Prima di costruire il front-end, vorremo costruire il back-end. Per ora manterremo il back-end semplice: utilizzeremo l'analisi del sentiment di base, pronta all'uso e il tagging di parte del discorso, insieme a un po' di pulizia dei dati per affrontare i problemi specifici del set di dati. Useremo una libreria NLP open source chiamata TextBlob e serviremo il risultato su Flask.

Analisi del sentimento, tagging di parti del discorso e NLP: una guida rapida

Se non hai mai lavorato con applicazioni di analisi del linguaggio naturale, questi termini potrebbero esserti molto estranei. NLP è un termine generico per le tecnologie che analizzano ed elaborano i dati del linguaggio umano naturale. Sebbene questo sia un argomento ampio, ci sono molte sfide comuni a tutte le tecnologie che affrontano quest'area. Ad esempio, il linguaggio umano, a differenza del linguaggio di programmazione o dei dati numerici, tende ad essere strutturato in modo lasco a causa della natura permissiva della grammatica del linguaggio umano. Inoltre, il linguaggio umano tende ad essere estremamente contestuale e una frase pronunciata o scritta in un contesto potrebbe non essere tradotta in un altro contesto. Infine, struttura e contesto a parte, il linguaggio è estremamente complesso. Le parole più in basso in un paragrafo potrebbero cambiare il significato di una frase all'inizio del paragrafo. Il vocabolario può essere inventato, ridefinito o modificato. Tutte queste complessità rendono le tecniche di analisi dei dati difficili da applicare in modo incrociato.

L'analisi del sentimento è un sottocampo della PNL che si concentra sulla comprensione dell'emotività di un passaggio del linguaggio naturale. Mentre l'emozione umana è intrinsecamente soggettiva, e quindi difficile da definire tecnologicamente, l'analisi del sentimento è un sottocampo che ha un'immensa promessa commerciale. Alcune applicazioni dell'analisi del sentimento includono la classificazione delle recensioni dei prodotti per identificare la valutazione positiva e negativa di varie caratteristiche, il rilevamento dell'umore di un'e-mail o di un discorso e il raggruppamento dei testi delle canzoni per stato d'animo. Se stai cercando una spiegazione più approfondita dell'analisi del sentimento, puoi leggere il mio articolo sulla creazione di un'applicazione basata sull'analisi del sentimento qui.

Il tagging di parte del discorso, o tagging POS, è un sottocampo molto diverso. L'obiettivo del tagging POS è identificare la parte del discorso di una determinata parola in una frase utilizzando informazioni grammaticali e contestuali. Identificare questa relazione è un compito molto più difficile di quanto sembri inizialmente: una parola può avere parti del discorso molto diverse in base al contesto e alla struttura della frase e le regole non sono sempre chiare nemmeno agli esseri umani. Fortunatamente, molti modelli standard oggi forniscono modelli potenti e versatili integrati con la maggior parte dei principali linguaggi di programmazione. Se vuoi saperne di più, puoi leggere il mio articolo sui tag POS qui.

Flask, TextBlob e Tweepy

Per il nostro back-end NLP, utilizzeremo Flask, TextBlob e Tweepy. Useremo Flask per costruire un server piccolo e leggero, TextBlob per eseguire la nostra elaborazione del linguaggio naturale e Tweepy per ottenere tweet dall'API di Twitter. Prima di iniziare a programmare, vorrai anche ottenere una chiave sviluppatore da Twitter in modo da poter recuperare i tweet.

Possiamo scrivere un back-end molto più sofisticato e utilizzare tecnologie NLP più complesse, ma per i nostri scopi odierni manterremo il back-end il più semplice possibile.

Codice di back-end

Ora siamo pronti per iniziare a programmare. Avvia il tuo editor e terminale Python preferito e iniziamo a craccare!

Innanzitutto, vorremo installare i pacchetti necessari.

 pip install flask flask-cors textblob tweepy python -m textblob.download_corpora

Ora, scriviamo il codice per la nostra funzionalità.

Apri un nuovo script Python, chiamalo server.py e importa le librerie richieste:

 import tweepy from textblob import TextBlob from collections import defaultdict

Scriviamo ora alcune funzioni di supporto:

 # simple, average a list of numbers with a guard clause to avoid division by zero def mean(lst): return sum(lst)/len(lst) if len(lst) > 0 else 0 # call the textblob sentiment analysis API and noun phrases API and return it as a dict def get_sentiment_and_np(sentence): blob = TextBlob(sentence) return{ 'sentiment': mean([s.sentiment.polarity for s in blob.sentences if s.sentiment.polarity != 0.0]), 'noun_phrases': list(blob.noun_phrases) } # use the tweepy API to get the last 50 posts from a user's timeline # We will want to get the full text if the text is truncated, and we will also remove retweets since they're not tweets by that particular account. def get_tweets(handle): auth = tweepy.OAuthHandler('YOUR_DEVELOPER_KEY') auth.set_access_token('YOUR_DEVELOPER_SECRET_KEY') api = tweepy.API(auth) tl = api.user_timeline(handle, count=50) tweets = [] for tl_item in tl: if 'retweeted_status' in tl_item._json: Continue # this is a retweet if tl_item._json['truncated']: status = api.get_status(tl_item._json['id'], tweet_mode='extended') # get full text tweets.append(status._json['full_text']) else: tweets.append(tl_item._json['text']) return tweets # http and https are sometimes recognized as noun phrases, so we filter it out. # We also try to skip noun phrases with very short words to avoid certain false positives # If this were a commercial app, we would want a more sophisticated filtering strategy. def good_noun_phrase(noun_phrase): noun_phrase_list = noun_phrase.split(' ') for np in noun_phrase_list: if np in {'http', 'https'} or len(np) < 3: return False return True

Ora che abbiamo scritto le funzioni di supporto, possiamo mettere insieme il tutto con un paio di semplici funzioni:

 # reshapes the tagged tweets into dictionaries that can be easily consumed by the front-end app def group_tweets(processed_tweets): # Sort it by sentiment sentiment_sorted = sorted(processed_tweets, key=lambda x: x['data']['sentiment']) # collect tweets by noun phrases. One tweet can be present in the list of more than one noun phrase, obviously. tweets_by_np = defaultdict(list) for pt in processed_tweets: for np in pt['data']['noun_phrases']: tweets_by_np[np].append(pt) grouped_by_np = {np.title(): tweets for np, tweets in tweets_by_np.items() if len(tweets) > 1 and good_noun_phrase(np)} return sentiment_sorted, grouped_by_np # download, filter, and analyze the tweets def download_analyze_tweets(accounts): processed_tweets = [] for account in accounts: for tweet in get_tweets(account): processed_tweet = ' '.join([i for i in tweet.split(' ') if not i.startswith('@')]) res = get_sentiment_and_np(processed_tweet) processed_tweets.append({ 'account': account, 'tweet': tweet, 'data': res }) sentiment_sorted, grouped_by_np = group_tweets(processed_tweets) return processed_tweets, sentiment_sorted, grouped_by_np

Ora puoi eseguire la funzione download_analyze_tweets su un elenco di handle che vuoi seguire e dovresti vedere i risultati.

Ho eseguito il seguente codice:

 if __name__ == '__main__': accounts = ['@spacex', '@nasa'] processed_tweets, sentiment_sorted, grouped_by_np = download_analyze_tweets(accounts) print(processed_tweets) print(sentiment_sorted) print(grouped_by_np)

L'esecuzione di questo ha prodotto i seguenti risultati. I risultati ovviamente dipendono dal tempo, quindi se vedi qualcosa di simile, sei sulla strada giusta.

 [{'account': '@spacex', 'tweet': 'Falcon 9… [{'account': '@nasa', 'tweet': 'Our Mars rove… {'Falcon': [{'account': '@spacex', 'tweet': 'Falc….

Ora possiamo costruire il server Flask, che è abbastanza semplice. Crea un file vuoto chiamato server.py e scrivi il seguente codice:

 from flask import Flask, request, jsonify from twitter import download_analyze_tweets from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/get_tweets', methods=['POST']) def get_tweets(): accounts = request.json['accounts'] processed_tweets, sentiment_sorted, grouped_by_np = download_analyze_tweets(accounts) return jsonify({ 'processedTweets': processed_tweets, 'sentimentSorted': sentiment_sorted, 'groupedByNp': grouped_by_np }) if __name__ == '__main__': app.run(debug=True)

Esegui il server e ora dovresti essere in grado di inviare una richiesta di post al server utilizzando un client HTTP a tua scelta. Passa {accounts: [“@NASA”, “@SpaceX”]} come argomento json e dovresti vedere che l'API restituisce qualcosa di simile a quello che è stato restituito nel codice di analisi di Twitter.

Ora che abbiamo un server, siamo pronti per codificare il front-end. A causa di una piccola sfumatura nella rete su un emulatore di telefono, ti consiglio di distribuire la tua API da qualche parte. Altrimenti, vorrai rilevare se la tua app è in esecuzione su un emulatore e inviare richieste a <Your Computer IP>:5000 invece di localhost:5000 quando si trova in un emulatore. Se distribuisci il codice, puoi semplicemente inviare la richiesta a quell'URL.

Ci sono molte opzioni per distribuire il server. Per un server di debug semplice e gratuito con una configurazione minima richiesta, consiglio qualcosa come PythonAnywhere, che dovrebbe essere in grado di eseguire questo server immediatamente.

Ora che abbiamo codificato il server back-end, diamo un'occhiata al front-end. Inizieremo con una delle opzioni più convenienti per uno sviluppatore web: Cordova.

Implementazione di Apache Cordova

Primer Cordova

Apache Cordova è una tecnologia software per aiutare gli sviluppatori web a raggiungere le piattaforme mobili. Sfruttando le funzionalità del browser Web implementate su piattaforme smartphone, Cordova racchiude il codice dell'applicazione Web in un contenitore di applicazioni nativo per creare un'applicazione. Cordova non è semplicemente un browser web di fantasia, tuttavia. Attraverso l'API Cordova, gli sviluppatori web possono accedere a molte funzionalità specifiche per smartphone come supporto offline, servizi di localizzazione e fotocamera sul dispositivo.

Per la nostra applicazione, scriveremo un'applicazione utilizzando React.js come framework JS e React-Bootstrap come framework CSS. Poiché Bootstrap è un framework CSS reattivo, ha già il supporto per l'esecuzione su schermi più piccoli. Una volta scritta l'applicazione, la compileremo in un'applicazione Web utilizzando Cordova.

Configurazione dell'app

Inizieremo facendo qualcosa di unico per configurare l'app Cordova React. In un articolo Medium , lo sviluppatore Shubham Patil spiega cosa stiamo facendo. In sostanza, stiamo configurando un ambiente di sviluppo React utilizzando React CLI, quindi un ambiente di sviluppo Cordova utilizzando Cordova CLI, prima di unire finalmente i due.

Per iniziare, esegui i seguenti due comandi nella cartella del codice:

 cordova create TwitterCurationCordova create-react-app twittercurationreact

Una volta completata la configurazione, vorremo spostare il contenuto della cartella public e src dell'app React nell'app Cordova. Quindi, nel package.json, copia gli script, l'elenco del browser e le dipendenze dal progetto React. Aggiungi anche "homepage": "./" nella radice del package.json per abilitare la compatibilità con Cordova.

Una volta unito il package.json, vorremo cambiare il file public/index.html in modo che funzioni con Cordova. Apri il file e copia i meta tag da www/index.html così come lo script alla fine del tag body quando Cordova.js viene caricato.

Quindi, modifica il file src/index.js per rilevare se è in esecuzione su Cordova. Se è in esecuzione su Cordova, vorremo eseguire il codice di rendering all'interno del gestore di eventi deviceready. Se è in esecuzione in un normale browser, esegui il rendering immediatamente.

 const renderReactDom = () => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.cordova) { document.addEventListener('deviceready', () => { renderReactDom(); }, false); } else { renderReactDom(); }

Infine, dobbiamo impostare la nostra pipeline di distribuzione. Aggiungi la definizione seguente nel file config.xml:

<hook type="before_prepare" src="hooks/prebuild.js" />

E inserisci il seguente script in prebuild.js:

 const path = require('path'); const { exec } = require('child_process'); const fs = require('fs'); const rimraf = require('rimraf'); function renameOutputFolder(buildFolderPath, outputFolderPath) { return new Promise((resolve, reject) => { fs.rename(buildFolderPath, outputFolderPath, (err) => { if (err) { reject(err); } else { resolve('Successfully built!'); } }); }); } function execPostReactBuild(buildFolderPath, outputFolderPath) { return new Promise((resolve, reject) => { if (fs.existsSync(buildFolderPath)) { if (fs.existsSync(outputFolderPath)) { rimraf(outputFolderPath, (err) => { if (err) { reject(err); return; } renameOutputFolder(buildFolderPath, outputFolderPath) .then(val => resolve(val)) .catch(e => reject(e)); }); } else { renameOutputFolder(buildFolderPath, outputFolderPath) .then(val => resolve(val)) .catch(e => reject(e)); } } else { reject(new Error('build folder does not exist')); } }); } module.exports = () => { const projectPath = path.resolve(process.cwd(), './node_modules/.bin/react-scripts'); return new Promise((resolve, reject) => { exec(`${projectPath} build`, (error) => { if (error) { console.error(error); reject(error); return; } execPostReactBuild(path.resolve(__dirname, '../build/'), path.join(__dirname, '../www/')) .then((s) => { console.log(s); resolve(s); }) .catch((e) => { console.error(e); reject(e); }); }); }); };

Questo esegue la build React e mette la cartella build nella posizione appropriata prima dell'avvio della build Cordova, automatizzando così il processo di distribuzione.

Ora possiamo provare a eseguire la nostra app. Esegui quanto segue nella riga di comando:

 npm install rimraf npm install npm run start

Dovresti vedere l'app React configurata e in esecuzione nel browser. Aggiungi Cordova ora:

 cordova platform add iOS cordova run iOS

E dovresti vedere l'app React in esecuzione nell'emulatore.

Configurazione del router e dei pacchetti

Per configurare parte dell'infrastruttura di cui abbiamo bisogno per creare l'app, iniziamo installando i pacchetti necessari:

 npm install react-bootstrap react-router react-router-dom

Ora imposteremo il routing e, mentre lo facciamo, imposteremo anche un semplice oggetto di stato globale che sarà condiviso da tutti i componenti. In un'applicazione di produzione, vorremo utilizzare un sistema di gestione dello stato come Redux o MobX, ma per ora lo terremo semplice. Vai su App.js e configura il percorso in questo modo:

 import { BrowserRouter as Router, Redirect, Route, } from "react-router-dom"; function App() { const [curatedTweets, setCuratedTweets] = useState(); return <Router> <Route path="/" exact render={() => <Input setCuratedTweets={setCuratedTweets} />} /> <Route path="/display" render={() => <Display curatedTweets={curatedTweets} />} /> <Route path="*" exact render={() => <Redirect to="/" />} /> </Router> }

Con questa definizione di percorso, abbiamo introdotto due percorsi che dovremo implementare: Input e Display. Si noti che la variabile curatedTweets viene passata a Display e la variabile setCuratedTweets viene passata a Input. Ciò significa che il componente di input sarà in grado di chiamare la funzione per impostare la variabile curatedTweets e Display otterrà la variabile da visualizzare.

Per iniziare a codificare i componenti, creiamo una cartella in /src chiamata /src/components. Sotto /src/components, crea un'altra cartella chiamata /src/components/input e due file sotto: input.js e input.css. Fai lo stesso per il componente Display: crea /src/components/display e sotto: display.js e display.css.

Sotto quelli, creiamo componenti stub, in questo modo:

 import React from 'react'; import 'input.css' const Input = () => <div>Input</div>; export default Input

E lo stesso per Display:

 import React from 'react'; import display.css' const Display = () => <div>Display</div>; export default Display

Con ciò, il nostro wireframing è completo e l'app dovrebbe essere eseguita. Ora codifichiamo la pagina Input.

Pagina di input

Piano d'insieme

Prima di scrivere il codice, pensiamo a cosa vogliamo che faccia la nostra pagina Input. Ovviamente, vorremo un modo per gli utenti di inserire e modificare gli handle di Twitter da cui vogliono estrarre. Vogliamo anche che gli utenti possano indicare che hanno finito. Quando gli utenti indicano che hanno finito, vorremo estrarre i tweet curati dalla nostra API di cura Python e infine passare al componente Display.

Ora che sappiamo cosa vogliamo che faccia il nostro componente, siamo pronti per programmare.

Configurazione del file

Iniziamo importando le librerie di React Router withRouter per ottenere l'accesso alla funzionalità di navigazione, i componenti React Bootstrap di cui abbiamo bisogno, in questo modo:

 import React, {useState} from 'react'; import {withRouter} from 'react-router-dom'; import {ListGroup, Button, Form, Container, Row, Col} from 'react-bootstrap'; import './input.css';

Ora definiamo la funzione stub per Input. Sappiamo che Input ottiene la funzione setCuratedTweets e vogliamo anche dargli la possibilità di navigare nel percorso di visualizzazione dopo aver impostato i tweet curati dalla nostra API Python. Pertanto, vorremo prendere dagli oggetti di scena setCuratedTweets e cronologia (per la navigazione).

 const Input = ({setCuratedTweets, history}) => { return <div>Input</div> }

Per dargli l'accesso all'API della cronologia, lo avvolgeremo con withRouter nella dichiarazione di esportazione alla fine del file:

 export default withRouter(Input);

Contenitori di dati

Impostiamo i contenitori di dati utilizzando React Hooks. Abbiamo già importato l'hook useState in modo da poter aggiungere il seguente codice al corpo del componente Input:

 const [handles, setHandles] = useState([]); const [handleText, setHandleText] = useState('');

Questo crea il contenitore e i modificatori per gli handle, che conterranno l'elenco degli handle da cui l'utente desidera estrarre, e handleText , che conterrà il contenuto della casella di testo che l'utente usa per inserire l'handle.

Ora, codifichiamo i componenti dell'interfaccia utente.

Componenti dell'interfaccia utente

I componenti dell'interfaccia utente saranno abbastanza semplici. Avremo una riga Bootstrap che contiene la casella di testo di input insieme a due pulsanti, uno per aggiungere il contenuto della casella di input corrente all'elenco degli handle e uno per estrarre dall'API. Avremo un'altra riga Bootstrap che mostra l'elenco degli handle che l'utente desidera estrarre utilizzando il gruppo di elenchi Bootstrap. Nel codice, sembra così:

 return ( <Container className="tl-container"> <Row> <Col> <Form.Control type="text" value={handleText} onChange={changeHandler} placeholder="Enter handle to pull" /> </Col> </Row> <Row className='input-row'> <Col> <Button variant="primary" onClick={getPull}>Pull</Button> {' '} <Button variant="success" onClick={onAddClicked}>Add</Button> </Col> </Row> <Row> <Col> <ListGroup className="handles-lg"> {handles.map((x, i) => <ListGroup.Item key={i}> {x} <span onClick={groupItemClickedBuilder(i)} className="delete-btn-span"> <Button variant="danger" size="sm"> delete </Button> </span> </ListGroup.Item>)} </ListGroup> </Col> </Row> </Container> );

Oltre al componente dell'interfaccia utente, vorremo implementare i tre gestori di eventi dell'interfaccia utente che gestiscono le modifiche ai dati. Il gestore di eventi getPull , che chiama l'API, verrà implementato nella sezione successiva.

 // set the handleText to current event value const textChangeHandler = (e) => { e.preventDefault(); setHandleText(e.target.value); } // Add handleText to handles, and then empty the handleText const onAddClicked = (e) => { e.preventDefault(); const newHandles = [...handles, handleText]; setHandles(newHandles); setHandleText(''); } // Remove the clicked handle from the list const groupItemClickedBuilder = (idx) => (e) => { e.preventDefault(); const newHandles = [...handles]; newHandles.splice(idx, 1); setHandles(newHandles); }

Ora siamo pronti per implementare la chiamata API.

Chiamata API

Per la chiamata API, vogliamo prendere gli handle che vogliamo estrarre, inviarli all'API Python in una richiesta POST e inserire il risultato JSON risultante nella variabile curatedTweets . Quindi, se tutto va bene, vogliamo passare in modo programmatico al percorso /display. In caso contrario, registreremo l'errore sulla console in modo da poter eseguire il debug più facilmente.

In modalità codice, si presenta così:

 const pullAPI = (e) => { e.preventDefault(); fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts: handles }) }).then(r=>r.json()).then(j => { setCuratedTweets(j); history.push('/display'); }) .catch(e => { console.log(e); }) }

E con questo, dovremmo essere a posto. Sentiti libero di eseguire l'app, aggiungere un paio di handle e inviare una richiesta all'API.

Ora siamo pronti per codificare la pagina del sentimento.

Modalità Ordinata Sentimento

Poiché l'API Python ordina già i tweet in base al sentimento, una volta ottenuto il risultato dall'API Python, la pagina del sentimento in realtà non è troppo difficile.

Piano d'insieme

Vorremo un'interfaccia elenco per mostrare i tweet. Vorremo anche che un paio di componenti di navigazione passino alla modalità di raggruppamento degli argomenti e tornino alla pagina di input.

Per iniziare, definiamo il sottocomponente della modalità SentimentDisplay nel file display.js.

Componente SentimentDisplay

SentimentDisplay prenderà l'oggetto curatedTweets e visualizzerà i tweet ordinati in base al sentimento in un elenco. Con l'aiuto di React-Bootstrap, il componente è abbastanza semplice:

 const SentimentDisplay = ({curatedTweets}) => { return <ListGroup> {curatedTweets.sentimentSorted.map((x, i) => <ListGroup.Item key={i}> <div className="account-div">{x.account}:</div> <span className="tweet-span">{x.tweet}</span> <span className="sentiments-span">({x.data.sentiment.toPrecision(2)})</span> </ListGroup.Item> )} </ListGroup> }

Già che ci siamo, aggiungiamo anche un po' di stile. Inserisci quanto segue in display.css e importalo:

 .account-div { font-size: 12px; font-weight: 600; } .tweet-span { font-size: 11px; font-weight: 500; } .sentiments-span { font-size: 10px; } .tl-container { margin-top: 10px; } .disp-row { margin-top: 5px; }

Ora possiamo mostrare il componente SentimentDisplay. Modifica la funzione Display in questo modo:

 const Display = ({curatedTweets}) => { return <SentimentDisplay curatedTweets={curatedTweets} /> };

Cogliamo anche questa opportunità per codificare i componenti di navigazione. Avremo bisogno di due pulsanti: il pulsante "Torna alla modifica" e la modalità gruppo di argomenti.

Possiamo implementare questi pulsanti in una riga Bootstrap separata proprio sopra il componente SentimentDisplay, in questo modo:

 Return <Container className="tl-container"> <Row> <Col> <Link to="/"><Button variant='primary'>Back</Button></Link> {' '} <Button variant='success'>View by Topic</Button> </Col> </Row> <Row className="disp-row"> <Col> <SentimentDisplay curatedTweets={curatedTweets} /> </Col> </Row> </Container>

Esegui l'app ed estrai i tweet da un paio di maniglie. Sembra piuttosto elegante!

Modalità di raggruppamento degli argomenti

Ora, vogliamo implementare la modalità di raggruppamento degli argomenti. È un po' più complesso del SentimentDisplay, ma ancora una volta, alcuni componenti Bootstrap molto utili ci aiutano immensamente.

Piano d'insieme

Otterremo tutte le frasi nominali e le visualizzeremo come un elenco di fisarmoniche. Quindi renderemo i tweet contenenti le frasi nominali una volta che l'elenco delle fisarmoniche sarà stato ampliato.

Implementazione del passaggio alla modalità di raggruppamento degli argomenti

Innanzitutto, implementiamo la logica per passare dalla modalità Sentiment alla modalità Raggruppamento argomenti. Iniziamo creando prima il componente stub:

 const TopicDisplay = () => { return <div>Topic Display</div> }

E imposta una logica per creare una modalità per visualizzarla. Nel componente di visualizzazione principale, aggiungi le seguenti righe per creare la logica per cui il componente viene visualizzato.

 // controls the display mode. Remember to import {useState} from 'react' const [displayType, setDisplayType] = useState('Sentiment'); // Switch the Display Mode const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } // determines the text on the mode switch button const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'

E cambia il JSX come segue per aggiungere la logica:

 Return <Container className="tl-container"> <Row> <Col> <Link to="/"><Button variant='primary'>Back</Button></Link> {' '} <Button variant='success' onClick={toggleDisplayType}>{switchStr}</Button> </Col> </Row> <Row className="disp-row"> <Col> { displayType === 'Sentiment'? <SentimentDisplay curatedTweets={curatedTweets} />: <TopicDisplay curatedTweets={curatedTweets} /> } </Col> </Row> </Container>

Ora dovresti vedere lo stub di visualizzazione del gruppo di argomenti quando passi.

Il componente TopicDisplay

Ora siamo pronti per codificare il componente TopicDisplay . Come discusso in precedenza, sfrutterà l'elenco di fisarmoniche Bootstrap. L'implementazione è in realtà abbastanza semplice:

 const TopicDisplay = ({curatedTweets}) => { return <Accordion> {Object.keys(curatedTweets.groupedByNp).map((x, i) => <Card key={i}> <Card.Header> <Accordion.Toggle as={Button} variant="link" eventKey={i}> {x} ({curatedTweets.groupedByNp[x].length}) </Accordion.Toggle> </Card.Header> <Accordion.Collapse eventKey={i}> <Card.Body> <ListGroup> {curatedTweets.groupedByNp[x].map((y, i2) => <ListGroup.Item key={i2}> <div className="account-div">{y.account}:</div> <span className="tweet-span">{y.tweet}</span> <span className="sentiments-span">({y.data.sentiment.toPrecision(2)})</span> </ListGroup.Item> )} </ListGroup> </Card.Body> </Accordion.Collapse> </Card> )} </Accordion> }

Esegui l'app e dovresti vedere la visualizzazione dell'argomento.

Ora l'app è pronta e siamo pronti per creare l'app per l'emulatore.

Esecuzione dell'app nell'emulatore

Cordova semplifica l'esecuzione dell'app nell'emulatore. Esegui semplicemente:

 cordova platform add ios # if you haven't done so already cordova run ios

E dovresti vedere l'app nell'emulatore. Poiché Bootstrap è un'app Web reattiva, l'app Web si adatta alla larghezza di un iPhone e tutto sembra abbastanza carino.

Con l'app Cordova completata, diamo ora un'occhiata all'implementazione di Ionic.

Implementazione della reazione ionica

Primer ionico

Ionic è una libreria di componenti Web e un toolkit CLI che semplifica la creazione di applicazioni ibride. Originariamente, Ionic era costruito su AngularJS e Cordova, ma da allora hanno rilasciato i loro componenti in React.js e hanno iniziato a supportare Capacitor, una piattaforma simile a Cordova. Ciò che distingue Ionic è che, anche se stai utilizzando componenti Web, i componenti sembrano molto simili a un'interfaccia mobile nativa. Inoltre, l'aspetto grafico dei componenti Ionic si adatta automaticamente al sistema operativo su cui viene eseguito, il che aiuta ancora una volta l'applicazione a sembrare più nativa e naturale. Infine, anche se questo esula dall'ambito del nostro articolo, Ionic fornisce anche diversi strumenti di compilazione che rendono un po' più semplice la distribuzione dell'applicazione.

Per la nostra applicazione, utilizzeremo i componenti React di Ionic per creare l'interfaccia utente sfruttando parte della logica JavaScript che abbiamo creato durante la sezione Cordova.

Configurazione dell'app

Innanzitutto, vorremo installare gli strumenti Ionic. Quindi eseguiamo quanto segue:

 npm install -g @Ionic/cli native-run cordova-res

E al termine dell'installazione, andiamo alla cartella del progetto. Ora utilizziamo la Ionic CLI per creare la nostra nuova cartella del progetto:

 ionic start twitter-curation-Ionic blank --type=react --capacitor

Guarda la magia accadere e ora vai nella cartella con:

 cd twitter-curation-Ionic

Ed esegui l'app vuota con:

 ionic serve

La nostra app è così configurata e pronta per l'uso. Definiamo alcuni percorsi.

Prima di andare avanti, noterai che Ionic ha avviato il progetto utilizzando TypeScript. Anche se non esco dal mio modo di usare TypeScript, ha alcune caratteristiche molto interessanti e lo useremo per questa implementazione.

Configurazione del router

Per questa implementazione utilizzeremo tre percorsi: input, sentimentDisplay e topicDisplay . Lo facciamo perché vogliamo sfruttare le funzionalità di transizione e navigazione fornite da Ionic e perché stiamo usando componenti Ionic e gli elenchi di fisarmoniche non sono preconfezionati con Ionic. Possiamo implementare il nostro, ovviamente, ma per questo tutorial rimarremo con i componenti Ionic forniti.

Se navighi su App.tsx, dovresti vedere i percorsi di base già definiti.

Pagina di input

Piano d'insieme

Utilizzeremo molta logica e codice simili all'implementazione Bootstrap, con alcune differenze chiave. Per prima cosa, useremo TypeScript, il che significa che avremo annotazioni di tipo per il nostro codice, che vedrai nella prossima sezione. In secondo luogo, utilizzeremo i componenti Ionic, che sono molto simili nello stile a Bootstrap ma saranno sensibili al sistema operativo nel suo stile. Infine, navigheremo dinamicamente utilizzando l'API della cronologia come nella versione Bootstrap, ma accedendo alla cronologia in modo leggermente diverso a causa dell'implementazione di Ionic Router.

Impostare

Iniziamo configurando il componente di input con un componente stub. Crea una cartella sotto le pagine denominata input e crea sotto di essa un file denominato Input.tsx. All'interno di quel file, inserisci il codice seguente per creare un componente React. Si noti che poiché stiamo usando TypeScript, è leggermente diverso.

 import React, {useState} from 'react'; const Input : React.FC = () => { return <div>Input</div>; } export default Input;

E cambia il componente in App.tsx in:

 const App: React.FC = () => ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> );

Ora, quando aggiorni l'app, dovresti vedere il componente stub di input.

Contenitori di dati

Creiamo ora i contenitori di dati. Vogliamo i contenitori per gli handle di Twitter inseriti così come il contenuto corrente della casella di input. Poiché stiamo usando TypeScript, dovremo aggiungere le annotazioni di tipo alla nostra useState nella funzione del componente:

 const Input : React.FC = () => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }

Vorremo anche un contenitore di dati per contenere i valori restituiti dall'API. Poiché il contenuto di ciò deve essere condiviso con gli altri percorsi, li definiamo a livello di App.tsx. Importa useState da React nel file App.tsx e modifica la funzione del contenitore dell'app come segue:

 const App: React.FC = () => { const [curatedTweets, setCuratedTweets] = useState<CuratedTweets>({} as CuratedTweets); return ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> ); }

A questo punto, se stai usando un editor con l'evidenziazione della sintassi come Visual Studio Code, dovresti vedere i CuratedTweets illuminarsi. Questo perché il file non sa come appare l'interfaccia di CuratedTweets. Definiamolo ora. Crea una cartella sotto src chiamata interfacce e crea un file al suo interno chiamato CuratedTweets.tsx. Nel file, definire l'interfaccia CuratedTweets come segue:

 interface TweetRecordData { noun_phrases: Array<string>, sentiment: number } export interface TweetRecord { account: string, data: TweetRecordData, tweet: string } export default interface CuratedTweets { groupedByNp: Record<string, Array<TweetRecord>>, processedTweets: Array<TweetRecord>, sentimentSorted: Array<TweetRecord> }

Ora l'app conosce la struttura dei dati di ritorno dell'API. Importa l'interfaccia CuratedTweets in App.tsx. Ora dovresti vedere la compilazione di App.tsx senza problemi.

Dobbiamo fare un altro paio di cose qui. Dobbiamo passare la funzione setCuratedTweets nel componente Input e rendere il componente Input consapevole di questa funzione.

In App.tsx, modifica il percorso di input in questo modo:

 <Route path="/input" render={() => <Input setCuratedTweets={setCuratedTweets}/>} exact={true} />

Ora dovresti vedere l'editor contrassegnare qualcos'altro: Input non sa che il nuovo prop gli è stato passato, quindi vorremo definirlo in Input.tsx.

First, import the CuratedTweets interface, then define the ContainerProps interface like so:

 interface ContainerProps { setCuratedTweets: React.Dispatch<React.SetStateAction<CuratedTweets>> }

And finally, change the Input component definition like so:

 const Input : React.FC<ContainerProps> = ({setCuratedTweets}) => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }

We are done defining the data containers, and now, onto building the UI components.

UI Components

For the UI component, we will want to build an input component and a list display component. Ionic provides some simple containers for these.

Let's start by importing the library components we'll be using:

 import { IonInput, IonItem, IonList, IonButton, IonGrid, IonRow, IonCol } from '@Ionic/react';

Now, we can replace the stub component with the IonInput , wrapped in an IonGrid:

 return <IonGrid> <IonRow> <IonCol> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> </IonRow> </IonGrid>

Notice that the event listener is onIonChange instead of onChange . Otherwise, it should look very familiar.

When you open the app in your browser, it may not look like the Bootstrap app. However, if you set your browser to emulator mode, the UI will make more sense. It will look even better once you deploy it on mobile, so look forward to it.

Now, let's add some buttons. We will want an “Add to list” button and a “Pull API” button. For that, we can use IonButton. Change the size of the input's IonCol to 8 and add the following two buttons with columns:

 <IonCol size="8"> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> <IonCol size="2"> <IonButton style={{float: 'right'}} color="primary" size="small" onClick={onAddClicked}>Add</IonButton> </IonCol> <IonCol size="2"> <IonButton style={{float: 'right'}} color="success" size="small" onClick={onPullClicked}>Pull</IonButton> </IonCol>

Since we're writing the buttons, let's write the event handlers as well.

The handler to add a Twitter handle to the list is simple:

 const onAddClicked = () => { if (text === undefined || text.length === 0) { return; } const newAccounts: Array<string> = [...accounts, text]; setAccounts(newAccounts); setText(''); }

We will implement the API call in the next section, so let's just put a stub function for onPullClicked :

 const onPullClicked = () => {}

Now, we need to write the component for displaying the list of handles that has been inputted by the user. For that, we will use IonList, put into a new IonRow:

 <IonRow> <IonCol> <IonList> {accounts.map((x:string, i:number) => <IonItem key={i}> <IonGrid> <IonRow> <IonCol size="8" style={{paddingTop: '12px'}}>{x}</IonCol> <IonCol><IonButton style={{float: 'right'}} color="danger" size="small" onClick={deleteClickedBuilder(i)}>Delete</IonButton></IonCol> </IonRow> </IonGrid> </IonItem>)} </IonList> </IonCol> </IonRow>

Each list item is displaying the handle and a delete button in its very own IonGrid. For this code to compile, we will want to implement the deleteClickedHandler as well. It should be very familiar from the previous section but with TypeScript annotations.

 const deleteClickedBuilder = (idx: number) => () => { const newAccounts: Array<string> = [...accounts]; newAccounts.splice(idx, 1); setAccounts(newAccounts); }

Save this, and you should see the Input page with all the UI components implemented. We can add handles, delete handles, and click the button to invoke the API.

As a final exercise, let's move the in-line styles to CSS. Create a file in the input folder called input.css and import it in the Input.tsx file. Then, add the following styles:

 .input-button { float: right; } .handle-display { padding-top: 12px; }

Now, add className="input-button” on all of the IonButtons and className=”handle-display” on the handle list item IonCol that is displaying the intended Twitter handle. Save the file, and you should see everything looking quite good.

API Call

The code to pull the API is very familiar from the previous section, with one exception - we have to get access to the history component to be able to dynamically change routes. We will do this using the withHistory hook.

We first import the hook:

 import { useHistory } from 'react-router';

And then implement the handler in the input component:

 const history = useHistory(); const switchToDisplay = () => { history.push('/display'); } const onPullClicked = () => { fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts }) }).then(r=>r.json()).then(j => { setCuratedTweets(j); switchToDisplay(); }) .catch(e => { console.log(e); }) }

Aggiunta di un'intestazione

Our Input page looks quite nice, but it looks a little bare due to Ionic's mobile-centric styling. To make the UI look more natural, Ionic provides a header feature that lets us provide a more natural user experience. When running on mobile, the header will also simulate the native OS's mobile platform, which makes the user experience even more natural.

Change your component import to:

 import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonInput, IonItem, IonList, IonButton, IonGrid, IonRow, IonCol } from '@Ionic/react';

And now wrap the UI in an Ionic page with a header, like so:

return <IonPage> <IonHeader> <IonToolbar> <IonTitle>Twitter Curation App</IonTitle> </IonToolbar> </IonHeader> <IonContent> <IonHeader collapse="condense"> <IonToolbar> <IonTitle size="large">Twitter Curation App</IonTitle> </IonToolbar> </IonHeader> <IonGrid> <IonRow> <IonCol size="8"> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> <IonCol size="2"> <IonButton className="input-button" color="primary" size="small" onClick={onAddClicked}>Add</IonButton> </IonCol> <IonCol size="2"> <IonButton className="input-button" color="success" size="small" onClick={onPullClicked}>Pull</IonButton> </IonCol> </IonRow> <IonRow> <IonCol> <IonList> {accounts.map((x:string, i:number) => <IonItem key={i}> <IonGrid> <IonRow> <IonCol size="8" className="handle-display">{x}</IonCol> <IonCol><IonButton className="input-button" color="danger" size="small" onClick={deleteClickedBuilder(i)}>Delete</IonButton></IonCol> </IonRow> </IonGrid> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid> </IonContent> </IonPage>

Ora sembra carino!

Pagina ordinata sentimento

Piano d'insieme

La pagina con ordinamento dei sentimenti sarà in gran parte simile alla pagina con ordinamento dei sentimenti dalla pagina Bootstrap ma utilizzando TypeScript e Ionic. Implementeremo anche la visualizzazione degli argomenti come percorso separato per sfruttare le funzionalità di navigazione di Ionic durante l'esecuzione su dispositivi mobili, quindi dovremo dare a questa pagina la possibilità di navigare anche verso la visualizzazione degli argomenti.

Configurazione del percorso

Iniziamo creando una nuova cartella chiamata sentimentsorted e un file chiamato SentimentSorted.tsx sotto. Esporta un componente stub in questo modo:

 import React from 'react'; const SentimentSorted: React.FC = () => { return <div>Sentiment Sorted</div> } export default SentimentSorted;

E in App.tsx, importa il componente:

 import SentimentSorted from './pages/sentimentsorted/SentimentSorted';

E aggiungi il percorso:

 <Route path="/display" render={() => <SentimentSorted curatedTweets={curatedTweets} />} />

Riceverai un errore TypeScript che dice che SentimentSorted non si aspetta gli oggetti di scena curatedTweets, quindi prendiamocene cura ora nella prossima sezione.

Componenti dell'interfaccia utente

Iniziamo definendo gli oggetti di scena del contenitore. Proprio come il componente di input:

 import CuratedTweets from '../../interfaces/CuratedTweets'; interface ContainerProps { curatedTweets: CuratedTweets }

E ora, cambia la visualizzazione dello stub:

 const SentimentSorted: React.FC<ContainerProps> = ({curatedTweets}) => { return <div>Sentiment Sorted</div> }

E tutto dovrebbe essere compilato.

Il display è molto semplice, è solo una IonList con i componenti del display:

 return <IonGrid> <IonRow> <IonCol> <IonList> {(curatedTweets.sentimentSorted).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid>

Se salvi e estrai alcuni tweet utilizzando il componente di input, dovresti vedere i tweet visualizzati in un elenco.

Ora aggiungiamo i pulsanti di navigazione. Aggiungi a IonGrid:

 <IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow>

Lo switchToInput è molto facile da implementare con l'API della cronologia:

 const switchToInput = () => { history.goBack(); }

E ToggleDisplayType dovrebbe essere familiare:

 const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'

Ora abbiamo implementato il componente SentimentDisplay . Ora, prima di implementare la Topic Display Page, dobbiamo implementare il componente che mostra tutti gli argomenti. Lo faremo nella prossima sezione.

Componente Gruppi di argomenti

Aggiungiamo un'opzione di visualizzazione dell'elenco di argomenti e la visualizziamo in modo condizionale. Per fare ciò, dobbiamo scomporre l'elenco di visualizzazione del sentimento. Rinomina SentimentDisplay in Display e analizziamo l'elenco di visualizzazione dei sentimenti:

 interface SentimentDisplayProps { sentimentSorted: Array<TweetRecord> } const SentimentDisplay: React.FC<SentimentDisplayProps> = ({sentimentSorted}) => { return <IonList> {(sentimentSorted || []).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> }

Nota come stiamo usando una delle definizioni di classe nell'interfaccia CuratedTweets. Questo perché questi componenti non necessitano dell'intero oggetto CuratedTweets ma solo di un sottoinsieme. L'elenco degli argomenti è molto simile:

 interface TopicDisplayProps { groupedByNP: Record<string, Array<TweetRecord>> } const TopicDisplay: React.FC<TopicDisplayProps> = ({groupedByNP}) => { return <IonList> {Object.keys(groupedByNP || {}).map((x, i) => <IonItem key={i} routerLink={`/topicDisplay/${encodeURIComponent(x)}`}> <IonLabel className="ion-text-wrap"> <h2>{x} ({groupedByNP[x].length})</h2> </IonLabel> </IonItem>)} </IonList> }

E ora, la visualizzazione condizionale è facile da configurare nel componente di visualizzazione:

 return ( <IonGrid> <IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow> { displayType === 'Sentiment'? <SentimentDisplay sentimentSorted={curatedTweets.sentimentSorted} /> : <TopicDisplay groupedByNP={curatedTweets.groupedByNp} /> } </IonGrid> );

Assicurati di modificare l'esportazione predefinita e ora siamo pronti per implementare la pagina di visualizzazione degli argomenti.

Pagina di visualizzazione dell'argomento

Piano d'insieme

La pagina di visualizzazione dell'argomento è una visualizzazione di elenco simile alla visualizzazione del sentimento, ma cercheremo l'argomento in questione dal parametro del percorso.

Configurazione del percorso

Se sei arrivato così lontano, dovresti già sapere cosa fare. Creiamo una cartella di pagina denominata topicdisplay e TopicDisplay.tsx, scriviamo un componente stub e importiamolo nella pagina App.tsx. Ora, impostiamo i percorsi:

 <Route path="/topicDisplay/:topic" render={() => <TopicDisplay curatedTweets={curatedTweets} /> } />

Ora siamo pronti per implementare il componente UI.

Componenti dell'interfaccia utente

Innanzitutto, creiamo la definizione di ContainerProps :

 interface ContainerProps { curatedTweets: CuratedTweets } const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { Return <div>Topic Display</div> }

Ora, dovremo recuperare l'argomento dal nome del percorso dell'URL. Per fare ciò, utilizzeremo l'API della cronologia. Quindi importiamo useHistory , istanziamo l'API della cronologia ed estraiamo l'argomento dal percorso. Già che ci siamo, implementiamo anche la funzionalità di ritorno:

 const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { const history = useHistory(); const switchToDisplay = () => { history.goBack(); } const topic = history.location.pathname.split('/')[2]; const tweets = (curatedTweets.groupedByNp || {})[topic];

Ora che abbiamo i tweet con quel particolare argomento, la visualizzazione è in realtà abbastanza semplice:

 return ( <IonGrid> <IonRow> <IonCol> <IonButton color='primary' onClick={switchToDisplay}>Back</IonButton> </IonCol> </IonRow> <IonRow> <IonCol> <IonList> {(tweets || []).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid> );

Salva ed esegui, e le cose dovrebbero andare bene.

Esecuzione dell'app nell'emulatore

Per eseguire l'app nell'emulatore, eseguiamo semplicemente alcuni comandi Ionic per aggiungere la piattaforma mobile e copiare il codice, in modo simile a come impostiamo le cose con Cordova.

 ionic build # builds the app ionic cap add ios # adds iOS as one of the platforms, only have to run once ionic cap copy # copy the build over ionic cap sync # only need to run this if you added native plugins ionic cap open ios # run the iOS emulator

E dovresti vedere l'app apparire.

Reagire all'implementazione nativa

React Native Primer

React Native adotta un approccio molto diverso dagli approcci basati sul web delle sezioni precedenti. React Native esegue il rendering del codice React come componenti nativi. Questo comporta diversi vantaggi. Innanzitutto, l'integrazione con il sistema operativo sottostante è molto più profonda, il che consente allo sviluppatore di sfruttare le nuove funzionalità dello smartphone e le funzionalità specifiche del sistema operativo che potrebbero non essere disponibili tramite Cordova/Capacitor. In secondo luogo, poiché non esiste un motore di rendering basato sul Web nel mezzo, un'app React Native è generalmente più veloce di una scritta utilizzando Cordova. Infine, poiché React Native consente l'integrazione di componenti nativi, gli sviluppatori possono esercitare un controllo molto più dettagliato sulla loro applicazione.

Per la nostra applicazione, utilizzeremo la logica delle sezioni precedenti e utilizzeremo una libreria di componenti React Native chiamata NativeBase per codificare la nostra interfaccia utente.

Configurazione dell'app

Innanzitutto, vorrai installare tutti i componenti necessari di React Native seguendo le istruzioni qui.

Una volta installato React Native, iniziamo il progetto:

 react-native init TwitterCurationRN

Lascia che lo script di installazione venga eseguito e, infine, la cartella dovrebbe essere creata. Cd nella cartella ed esegui run-ios native di reazione, e dovresti vedere l'emulatore pop-up con l'app di esempio.

Vorremo anche installare NativeBase poiché questa è la nostra libreria di componenti. Per farlo, eseguiamo:

 npm install --save native-base react-native link

Vogliamo anche installare il navigatore dello stack React Native. Corriamo:

 npm install --save @react-navigation/stack @react-navigation/native

e

 react-native link cd ios pod-install cd

Per completare il collegamento e l'installazione dei plugin nativi.

Configurazione del router

Per il routing, utilizzeremo lo stack navigator che abbiamo installato nel passaggio precedente.

Importa i componenti del router:

 import { NavigationContainer } from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack';

E ora creiamo uno stack navigator:

 const Stack = createStackNavigator();

Modifica il contenuto del componente App per utilizzare il navigatore dello stack:

 const App = () => { return ( <> <StatusBar bar /> <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> </Stack.Navigator> </NavigationContainer> </> ); };

A questo punto, riceverai un errore perché Entry non è ancora definito. Definiamo uno stub element solo per renderlo felice.

Crea una cartella dei componenti nel tuo progetto, crea un file chiamato Entry.jsx e aggiungi un componente stub in questo modo:

 import React, {useState} from 'react'; import { Text } from 'native-base'; export default Entry = ({navigation}) => { return <Text>Entry</Text>; // All text must be wrapped in a <Text /> component or <TextView /> if you're not using NativeBase. }

Importa il componente Entry nella tua app e dovrebbe essere compilato.

Ora siamo pronti per codificare la pagina Input.

Pagina di input

Piano d'insieme

Implementeremo una pagina molto simile a quella implementata sopra, ma utilizzando i componenti NativeBase. La maggior parte delle API JavaScript e React che abbiamo utilizzato, come hook e fetch, sono ancora disponibili.

L'unica differenza sarà il modo in cui lavoriamo con l'API di navigazione, che vedrai più avanti.

Componenti dell'interfaccia utente

I componenti aggiuntivi di NativeBase che utilizzeremo sono Container, Content, Input, List, ListItem e Button. Questi hanno tutti analoghi in Ionic e Bootstrap React e i costruttori di NativeBase lo hanno reso molto intuitivo per le persone che hanno familiarità con queste librerie. Importa semplicemente in questo modo:

 import { Container, Content, Input, Item, Button, List, ListItem, Text } from 'native-base';

E il componente è:

 return <Container> <Content> <Item regular> <Input placeholder='Input Handles Here' onChangeText={inputChange} value={input} /> <Button primary onPress={onAddClicked}><Text> Add </Text></Button> <Text> </Text> <Button success onPress={onPullClicked}><Text> Pull </Text></Button> </Item> <Item> <List style={{width: '100%'}}> {handles.map((item) => <ListItem key={item.key}> <Text>{item.key}</Text> </ListItem>)} </List> </Item> </Content> </Container>

E ora, implementiamo i gestori di stato ed eventi:

 const [input, changeInput] = useState(''); const [handles, changeHandles] = useState([]); const inputChange = (text) => { changeInput(text); } const onAddClicked = () => { const newHandles = [...handles, {key: input}]; changeHandles(newHandles); changeInput(''); }

E infine, la chiamata API:

 const onPullClicked = () => { fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts: handles.map(x => x.key) }) }).then(r=>r.json()).then(data => { navigation.navigate('SentimentDisplay', { data }); }) .catch(e => { console.log(e); }) }

Si noti che questa implementazione è la stessa dell'implementazione NativeBase, tranne per il fatto che stiamo navigando in un modo diverso. Lo stack navigator trasferisce nei suoi componenti un prop chiamato "navigazione" e che può essere utilizzato per navigare tra le rotte con .navigate . Oltre alla semplice navigazione, puoi anche trasferire i dati al componente di destinazione. Useremo questo meccanismo per passare i dati.

Per fare in modo che l'app venga compilata, dobbiamo comunque rendere Entry consapevole del componente di navigazione. Per fare ciò, dovremo modificare la dichiarazione della funzione componente:

 export default Entry = ({navigation}) => {

Ora salva e dovresti vedere la pagina.

Pagina ordinata sentimento

Piano d'insieme

Implementeremo la pagina dei sentimenti in modo molto simile alle sezioni precedenti, ma modelleremo la pagina in modo leggermente diverso e utilizzeremo anche la libreria di navigazione in modo diverso per ottenere il valore restituito dalla chiamata API.

Poiché React Native non ha CSS, dovremo definire un oggetto StyleSheet o semplicemente codificare gli stili in linea. Poiché condivideremo parte dello stile tra i componenti, creiamo un foglio di stile globale. Lo faremo dopo la configurazione del percorso.

Inoltre, poiché StackNavigator ha un pulsante di navigazione Indietro integrato, non sarà necessario implementare il nostro pulsante Indietro.

Configurazione del percorso

La definizione del percorso in StackNavigator è molto semplice. Ne creiamo semplicemente uno nuovo chiamato Stack Screen e gli diamo il componente, proprio come il router React.

 <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> </Stack.Navigator> </NavigationContainer>

Per farlo funzionare, dovremo ovviamente creare un componente stub in components/SentimentDisplay.js:

 import React from 'react'; import {Text} from 'native-base'; const SentimentDisplay = () => { return <Text>Sentiment Display</Text>; } export default SentimentDisplay;

E importalo:

 import SentimentDisplay from './components/SentimentDisplay';

Ora siamo pronti per creare il foglio di stile globale.

Foglio di stile globale

Innanzitutto, crea un file denominato globalStyles.js. Quindi, importa il componente StyleSheet da React Native e definisci gli stili:

 import {StyleSheet} from 'react-native'; export default StyleSheet.create({ tweet: {paddingTop: 5}, accountName: {fontWeight: '600'}, })

E siamo pronti per codificare l'interfaccia utente.

Componenti dell'interfaccia utente

Il componente dell'interfaccia utente è abbastanza familiare, con l'eccezione di come lavoriamo con i percorsi. Vorremo utilizzare la navigazione e il percorso speciali di StackNavigator per ottenere lo stato corrente dell'applicazione e per passare alla visualizzazione dell'argomento se l'utente desidera vedere quella pagina.

Modifica la definizione del componente per accedere ai prop di navigazione:

 const SentimentDisplay = ({route, navigation}) => {

E ora, implementiamo la lettura dello stato dell'applicazione e la funzionalità di navigazione:

 const {params: {data}} = route; const viewByTopicClicked = () => { navigation.navigate('TopicDisplay', { data }); }

Importa gli stili globali:

 import globalStyles from './globalStyles';

E i componenti:

 import { View } from 'react-native'; import {List, Item, Content, ListItem, Container, Text, Button} from 'native-base';

E infine i componenti:

 return <Container> <Content> <Item> <Button primary onPress={viewByTopicClicked}><Text>View By Topic</Text></Button> </Item> <Item> <List style={{width: '100%'}}> {data.sentimentSorted.map((item, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <Text style={globalStyles.accountName}>{item.account}:</Text> <Text style={globalStyles.tweet}>{item.tweet} ({item.data.sentiment.toPrecision(2)})</Text> </View> </ListItem>)} </List> </Item> </Content> </Container>;

Salva e prova a estrarre alcuni tweet e dovresti vedere la visualizzazione del sentimento. Ora nella pagina di raggruppamento degli argomenti.

Pagina Raggruppamento argomenti

Piano d'insieme

La visualizzazione degli argomenti è di nuovo molto simile. Utilizzeremo un generatore di gestori per creare una funzione di navigazione per passare alla pagina di visualizzazione di un argomento specifico e definiremo anche un foglio di stile specifico per questa pagina.

Una nuova cosa che faremo è l'implementazione di TouchableOpacity, che è un componente specifico di React Native che funziona in modo molto simile a un pulsante.

Configurazione del percorso

La definizione del percorso è la stessa di prima:

 <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> <Stack.Screen name="TopicDisplay" component={TopicDisplay} /> </Stack.Navigator>

I componenti del componente stub/TopicDisplay.js:

 import React from 'react'; import {Text} from 'native-base'; const TopicDisplay = () => { return <Text>Topic Display</Text>; } export default TopicDisplay;

E importalo:

 import TopicDisplay from './components/TopicDisplay;

Componenti dell'interfaccia utente

Molto di questo sembrerà molto familiare. Importa le funzioni della libreria:

 import { View, TouchableOpacity, StyleSheet } from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base';

Importa gli stili globali:

 import globalStyles from './globalStyles';

Definisci gli stili personalizzati:

 const styles = StyleSheet.create({ topicName: {fontWeight: '600'}, })

Definisci gli oggetti di navigazione:

 export default TopicDisplay = ({route, navigation}) => {

Definire i dati e i gestori delle azioni. Si noti che stiamo usando un generatore di gestori, una funzione che restituisce una funzione:

 const {params: {data}} = route; const specificItemPressedHandlerBuilder = (topic) => () => { navigation.navigate('TopicDisplayItem', { data, topic }); }

E ora, i componenti. Si noti che stiamo usando TouchableOpacity, che può avere un gestore onPress . Avremmo potuto usare anche TouchableTransparency, ma l'animazione click-and-hold di TouchableOpacity era più adatta per la nostra applicazione.

 return <Container> <Content> <Item> <List style={{width: '100%'}}> {Object.keys(data.groupedByNp).map((topic, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <TouchableOpacity onPress={specificItemPressedHandlerBuilder(topic)}> <Text style={styles.topicName}>{topic}</Text> </TouchableOpacity> </View> </ListItem>)} </List> </Item> </Content> </Container>;

E questo dovrebbe farlo. Salva e prova l'applicazione!

Ora, nella pagina dell'elemento di visualizzazione dell'argomento.

Argomento Visualizza la pagina dell'elemento

Piano d'insieme

La pagina dell'elemento di visualizzazione dell'argomento è molto simile e tutte le idiosincrasie vengono gestite nelle altre sezioni, quindi dovrebbe essere facile navigare da qui.

Configurazione del percorso

Aggiungeremo la definizione del percorso:

 <Stack.Screen name="TopicDisplayItem" component={TopicDisplayItem} />

Aggiungi l'importazione:

 import TopicDisplayItem from './components/TopicDisplayItem';

E crea il componente stub. Invece di un semplice componente, importiamo anche i componenti NativeBase che utilizzeremo e definiamo i prop del percorso:

 import React from 'react'; import {View} from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base'; import globalStyles from './globalStyles'; const TopicDisplayItem = ({route}) => { const {params: {data, topic}} = route; return <Text>Topic Display Item</Text>; } export default TopicDisplayItem;

Componenti dell'interfaccia utente

Il componente dell'interfaccia utente è abbastanza semplice. L'abbiamo già visto e non stiamo implementando alcuna logica personalizzata. Quindi, proviamoci! Fai un respiro profondo…

 return <Container> <Content> <Item> <List style={{width: '100%'}}> {data.groupedByNp[topic].map((item, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <Text style={globalStyles.accountName}>{item.account}:</Text> <Text style={globalStyles.tweet}>{item.tweet} ({item.data.sentiment.toPrecision(2)})</Text> </View> </ListItem>)} </List> </Item> </Content> </Container>;

Salva e dovremmo essere a posto! Ora siamo pronti per eseguire l'app nell'emulatore... aspetta, non l'abbiamo già fatto?

Esecuzione dell'app

Bene, dal momento che stai lavorando con React Native, stai già eseguendo l'app nell'emulatore, quindi questa parte è già stata curata. Questa è una delle grandi cose dell'ambiente di sviluppo di React Native.

Accidenti! Con questo, abbiamo finito con la parte di codifica di questo articolo. Vediamo cosa abbiamo imparato sulle tecnologie.

Confronto tra le tecnologie

Cordova: pro e contro

La cosa migliore di Cordova è la velocità con cui uno sviluppatore web esperto può programmare qualcosa di funzionale e ragionevolmente presentabile. Le capacità e l'esperienza di sviluppo Web si trasferiscono facilmente perché, dopo tutto, stai codificando un'app Web. Il processo di sviluppo è rapido e semplice e anche l'accesso all'API Cordova è semplice e intuitivo.

Gli svantaggi dell'utilizzo diretto di Cordova derivano principalmente dall'eccessiva dipendenza dai componenti web. Gli utenti si aspettano un'esperienza utente e un design dell'interfaccia specifici quando utilizzano app mobili e quando un'applicazione sembra un sito mobile, l'esperienza può essere un po' stridente. Inoltre, la maggior parte delle funzionalità integrate nelle app, come le animazioni di transizione e le utilità di navigazione, devono essere implementate manualmente.

Ionico: pro e contro

La parte migliore di Ionic è stata quante funzionalità incentrate sui dispositivi mobili ho ottenuto "gratuitamente". Codificando in modo molto simile a come avrei codificato un'applicazione Web, sono stato in grado di creare un'app che sembra molto più adatta ai dispositivi mobili rispetto al semplice utilizzo di Cordova e React-Bootstrap. C'erano animazioni di navigazione, pulsanti con stili dall'aspetto nativo e molte opzioni dell'interfaccia utente che rendevano l'esperienza utente molto fluida.

Lo svantaggio dell'utilizzo di Ionic è stato in parte causato dai suoi punti di forza. Innanzitutto, a volte era difficile immaginare come si sarebbe comportata l'app in vari ambienti. Solo perché l'app sembrava in un modo non significava che lo stesso posizionamento dell'interfaccia utente sarebbe stato lo stesso in un altro ambiente. In secondo luogo, Ionic si trova in cima a molte tecnologie sottostanti e ottenere l'accesso ad alcuni componenti si è rivelato difficile. Infine, questo è specifico di Ionic-React, ma poiché Ionic è stato creato per la prima volta per Angular, molte funzionalità di Ionic-React sembravano avere meno documentazione e supporto. Tuttavia, il team di Ionic sembra molto attento alle esigenze degli sviluppatori di React e fornisce rapidamente nuove funzionalità.

Reagire nativo: pro e contro

React Native ha sviluppato un'esperienza utente molto fluida su dispositivi mobili. Collegandosi direttamente all'emulatore, non era un mistero come sarebbe stata l'applicazione. L'interfaccia del debugger basata sul Web è stata estremamente utile nell'applicazione incrociata delle tecniche di debug dal mondo delle applicazioni Web e l'ecosistema è piuttosto robusto.

Lo svantaggio di React Native deriva dalla sua vicinanza all'interfaccia nativa. Non è stato possibile utilizzare molte librerie basate su DOM, il che significava dover apprendere nuove librerie e migliori pratiche. Senza il vantaggio dei CSS, lo stile dell'applicazione era in qualche modo meno intuitivo. Infine, con molti nuovi componenti da imparare (ad es. Visualizza invece di div, Componente di testo che avvolge tutto, Pulsanti vs. TouchableOpacity vs. TouchableTransparency, ecc.), c'è un po' di una curva di apprendimento all'inizio se qualcuno sta entrando nel Reagisci nel mondo nativo con poca conoscenza preliminare della meccanica.

Quando utilizzare ciascuna tecnologia

Poiché Cordova, Ionic e React Native hanno tutti vantaggi e svantaggi molto forti, ogni tecnologia ha un contesto in cui godrebbe della migliore produttività e prestazioni.

Se disponi già di un'applicazione web-first con una forte identità di marca che circonda il design dell'interfaccia utente e l'aspetto generale, la tua migliore opzione sarebbe Cordova, che ti dà accesso alle funzionalità native dello smartphone permettendoti di riutilizzare la maggior parte del tuo componenti web e preservare l'identità del tuo marchio nel processo. Per applicazioni relativamente semplici che utilizzano un framework reattivo, potresti essere in grado di creare un'app mobile con pochissime modifiche richieste. Tuttavia, la tua applicazione sembrerà meno un'app e più una pagina Web e alcuni dei componenti che le persone si aspettano da un'app mobile saranno codificati separatamente. Pertanto, consiglio Cordova nei casi in cui ti trovi in ​​un progetto web-first che porta l'applicazione su dispositivi mobili.

Se stai iniziando a programmare una nuova applicazione con una filosofia incentrata sull'app, ma le competenze del tuo team riguardano principalmente lo sviluppo web, ti consiglio Ionic. La libreria di Ionic ti consente di scrivere rapidamente codice che si avvicina e si sente vicino ai componenti nativi mentre ti consente di applicare le tue abilità e il tuo istinto come sviluppatore web. Ho scoperto che le migliori pratiche di sviluppo web si applicavano prontamente allo sviluppo con Ionic e lo stile con CSS funzionava perfettamente. Inoltre, la versione mobile del sito sembra molto più nativa di un sito Web codificato utilizzando un framework CSS reattivo. Tuttavia, in alcuni passaggi, ho scoperto che l'integrazione dell'API React-Ionic-Native richiede alcune regolazioni manuali, che potrebbero richiedere molto tempo. Pertanto, consiglio Ionic nei casi in cui la tua applicazione viene sviluppata da zero e desideri condividere una quantità significativa di codice tra un'applicazione Web compatibile con dispositivi mobili e un'applicazione mobile.

Se stai codificando una nuova applicazione con una base di codice nativa implementata, potresti provare React Native. Anche se non stai utilizzando codice nativo, potrebbe anche essere l'opzione migliore nei casi in cui hai già familiarità con React Native o quando la tua preoccupazione principale è l'applicazione mobile piuttosto che un'applicazione ibrida. Avendo concentrato la maggior parte dei miei sforzi di sviluppo front-end sullo sviluppo web, inizialmente ho scoperto che iniziare con React Native aveva più una curva di apprendimento rispetto a Ionic o Cordova a causa delle differenze nell'organizzazione dei componenti e nelle convenzioni di codifica. Tuttavia, una volta apprese queste sfumature, l'esperienza di codifica è abbastanza fluida, soprattutto con l'aiuto di una libreria di componenti come NativeBase. Data la qualità dell'ambiente di sviluppo e il controllo sull'applicazione, se il front-end del tuo progetto è principalmente un'applicazione mobile, consiglierei React Native come strumento preferito.

Argomenti futuri

Uno degli argomenti che non ho avuto il tempo di esplorare è stata la facilità di accesso alle API native come fotocamera, geolocalizzazione o autenticazione biometrica. Uno dei grandi vantaggi dello sviluppo mobile è l'accessibilità di un ricco ecosistema di API che generalmente non è accessibile sul browser.

Negli articoli futuri, vorrei esplorare la facilità di sviluppo di queste applicazioni abilitate per API native utilizzando varie tecnologie multipiattaforma.

Conclusione

Oggi abbiamo implementato un'app per la cura di Twitter utilizzando tre diverse tecnologie di sviluppo mobile multipiattaforma. Spero che questo ti abbia dato un'idea di com'è ogni tecnologia e ti abbia ispirato a sviluppare la tua applicazione basata su React.

Grazie per aver letto!