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

Publié: 2022-03-11

Au cours des 15 années environ qui se sont écoulées depuis qu'Apple a lancé le premier iPhone, le paysage du développement logiciel a radicalement changé. Alors que les smartphones sont de plus en plus adoptés et continuent de se développer en capacités uniques, les utilisateurs préfèrent de plus en plus accéder aux services logiciels via des appareils mobiles plutôt que des ordinateurs de bureau ou des ordinateurs portables. Les smartphones offrent des fonctionnalités telles que la géolocalisation, l'authentification biométrique et la détection de mouvement, dont beaucoup commencent à peine à être copiées par les plates-formes de bureau. Dans certains groupes démographiques, un smartphone ou un appareil mobile similaire est le principal moyen de consommation de logiciels, contournant complètement les ordinateurs.

Les entreprises ont remarqué ce changement et l'ont renforcé de manière majeure. Les applications mobiles ne sont plus une réflexion après coup. Des applications allant de Robinhood, une société de courtage financier, à Instagram, une société de médias sociaux, en passant par Uber, une société de covoiturage, adoptent une stratégie de développement axée sur le mobile. S'il existe une application de bureau, elle est souvent proposée en complément de l'application mobile, plutôt que comme objectif principal.

Pour les développeurs full-stack, il est crucial de s'adapter à ces tendances changeantes. Heureusement, il existe de nombreuses technologies matures et bien prises en charge pour aider les développeurs Web à appliquer leurs compétences au développement mobile. Aujourd'hui, nous allons explorer trois de ces technologies : Cordova, Ionic et React Native. Nous utiliserons React.js, l'un des frameworks les plus populaires pour le développement Web frontal, comme technologie de développement de base. Bien que nous nous concentrions sur le développement d'une application iPhone, il s'agit de technologies multiplateformes et elles seront compilables sur la plateforme Android.

Ce que nous allons construire aujourd'hui

Nous allons créer une application qui utilise le traitement du langage naturel (NLP) pour traiter et gérer les flux Twitter. L'application permettra à l'utilisateur de sélectionner un ensemble de descripteurs Twitter, d'extraire les mises à jour les plus récentes à l'aide d'une API Twitter et de classer les tweets en fonction du sentiment et du sujet. L'utilisateur pourra alors voir les tweets en fonction du sentiment ou du sujet.

Back-End

Avant de construire le front-end, nous voudrons construire le back-end. Nous garderons le back-end simple pour l'instant - nous utiliserons une analyse des sentiments de base prête à l'emploi et un marquage des parties du discours, ainsi qu'un peu de nettoyage des données pour traiter les problèmes spécifiques à l'ensemble de données. Nous utiliserons une bibliothèque NLP open-source appelée TextBlob et servirons le résultat sur Flask.

Analyse des sentiments, étiquetage des parties du discours et PNL : une introduction rapide

Si vous n'avez jamais travaillé avec des applications d'analyse du langage naturel auparavant, ces termes peuvent vous être très étrangers. La PNL est un terme générique désignant les technologies qui analysent et traitent les données du langage humain naturel. Bien qu'il s'agisse d'un vaste sujet, de nombreux défis sont communs à toutes les technologies qui s'attaquent à ce domaine. Par exemple, le langage humain, contrairement au langage de programmation ou aux données numériques, a tendance à être vaguement structuré en raison de la nature permissive de la grammaire du langage humain. De plus, le langage humain a tendance à être extrêmement contextuel, et une phrase prononcée ou écrite dans un contexte peut ne pas se traduire dans un autre contexte. Enfin, structure et contexte mis à part, le langage est extrêmement complexe. Les mots situés plus bas dans un paragraphe peuvent modifier le sens d'une phrase au début du paragraphe. Le vocabulaire peut être inventé, redéfini ou modifié. Toutes ces complexités rendent les techniques d'analyse de données difficiles à appliquer de manière croisée.

L'analyse des sentiments est un sous-domaine de la PNL qui se concentre sur la compréhension de l'émotivité d'un passage en langage naturel. Alors que l'émotion humaine est intrinsèquement subjective, et donc difficile à cerner technologiquement, l'analyse des sentiments est un sous-domaine qui a d'immenses promesses commerciales. Certaines applications de l'analyse des sentiments incluent la classification des critiques de produits pour identifier l'évaluation positive et négative de diverses fonctionnalités, la détection de l'humeur d'un e-mail ou d'un discours et le regroupement des paroles de chansons par humeur. Si vous recherchez une explication plus approfondie de l'analyse des sentiments, vous pouvez lire mon article sur la création d'une application basée sur l'analyse des sentiments ici.

Le marquage de la partie du discours, ou marquage POS, est un sous-domaine très différent. L'objectif du marquage POS est d'identifier la partie du discours d'un mot donné dans une phrase à l'aide d'informations grammaticales et contextuelles. Identifier cette relation est une tâche beaucoup plus difficile qu'il n'y paraît au départ - un mot peut avoir des parties de discours très différentes en fonction du contexte et de la structure de la phrase, et les règles ne sont pas toujours claires, même pour les humains. Heureusement, de nombreux modèles prêts à l'emploi fournissent aujourd'hui des modèles puissants et polyvalents intégrés à la plupart des principaux langages de programmation. Si vous souhaitez en savoir plus, vous pouvez lire mon article sur le marquage POS ici.

Flacon, TextBlob et Tweepy

Pour notre back-end NLP, nous utiliserons Flask, TextBlob et Tweepy. Nous utiliserons Flask pour créer un petit serveur léger, TextBlob pour exécuter notre traitement du langage naturel et Tweepy pour obtenir des tweets de l'API Twitter. Avant de commencer à coder, vous souhaiterez également obtenir une clé de développeur de Twitter afin de pouvoir récupérer les tweets.

Nous pouvons écrire un back-end beaucoup plus sophistiqué et utiliser des technologies NLP plus complexes, mais pour nos besoins actuels, nous garderons le back-end aussi simple que possible.

Code principal

Maintenant, nous sommes prêts à commencer à coder. Lancez votre éditeur et terminal Python préféré, et commençons !

Tout d'abord, nous voudrons installer les packages requis.

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

Maintenant, écrivons le code de notre fonctionnalité.

Ouvrez un nouveau script Python, appelez-le server.py et importez les bibliothèques requises :

 import tweepy from textblob import TextBlob from collections import defaultdict

Écrivons maintenant quelques fonctions d'assistance :

 # 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

Maintenant que nous avons écrit les fonctions d'assistance, nous pouvons tout assembler avec quelques fonctions simples :

 # 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

Vous pouvez maintenant exécuter la fonction download_analyze_tweets sur une liste de descripteurs que vous souhaitez suivre, et vous devriez voir les résultats.

J'ai exécuté le code suivant :

 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'exécution de ceci a produit les résultats suivants. Les résultats dépendent évidemment du temps, donc si vous voyez quelque chose de similaire, vous êtes sur la bonne voie.

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

Nous pouvons maintenant créer le serveur Flask, ce qui est assez simple. Créez un fichier vide appelé server.py et écrivez le code suivant :

 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)

Exécutez le serveur et vous devriez maintenant pouvoir envoyer une demande de publication au serveur à l'aide d'un client HTTP de votre choix. Transmettez {accounts: ["@NASA", "@SpaceX"]} comme argument json, et vous devriez voir l'API renvoyer quelque chose de similaire à ce qui a été renvoyé dans le code d'analyse de Twitter.

Maintenant que nous avons un serveur, nous sommes prêts à coder le front-end. En raison d'une petite nuance dans la mise en réseau sur un émulateur téléphonique, je vous recommande de déployer votre API quelque part. Sinon, vous devrez détecter si votre application s'exécute sur un émulateur et envoyer des requêtes à <Your Computer IP>:5000 au lieu de localhost:5000 lorsqu'elle se trouve dans un émulateur. Si vous déployez le code, vous pouvez simplement envoyer la demande à cette URL.

Il existe de nombreuses options pour déployer le serveur. Pour un serveur de débogage simple et gratuit avec une configuration minimale requise, je recommande quelque chose comme PythonAnywhere, qui devrait pouvoir exécuter ce serveur prêt à l'emploi.

Maintenant que nous avons codé le serveur back-end, regardons le front-end. Nous allons commencer par l'une des options les plus pratiques pour un développeur Web : Cordova.

Implémentation d'Apache Cordova

Abécédaire de Cordoue

Apache Cordova est une technologie logicielle qui aide les développeurs Web à cibler les plates-formes mobiles. En tirant parti des fonctionnalités de navigateur Web mises en œuvre sur les plates-formes de smartphone, Cordova encapsule le code d'application Web dans un conteneur d'application natif pour créer une application. Cependant, Cordova n'est pas simplement un navigateur Web sophistiqué. Grâce à l'API Cordova, les développeurs Web peuvent accéder à de nombreuses fonctionnalités spécifiques aux smartphones, telles que l'assistance hors ligne, les services de localisation et la caméra sur l'appareil.

Pour notre application, nous allons écrire une application utilisant React.js comme framework JS et React-Bootstrap comme framework CSS. Étant donné que Bootstrap est un framework CSS réactif, il prend déjà en charge l'exécution sur des écrans plus petits. Une fois l'application écrite, nous la compilerons dans une application Web en utilisant Cordova.

Configuration de l'application

Nous allons commencer par faire quelque chose d'unique pour configurer l'application Cordova React. Dans un article Medium , le développeur Shubham Patil explique ce que nous faisons. Essentiellement, nous mettons en place un environnement de développement React à l'aide de la CLI React, puis un environnement de développement Cordova à l'aide de la CLI Cordova, avant de finalement fusionner les deux.

Pour commencer, exécutez les deux commandes suivantes dans votre dossier de code :

 cordova create TwitterCurationCordova create-react-app twittercurationreact

Une fois la configuration terminée, nous voudrons déplacer le contenu du dossier public et src de l'application React vers l'application Cordova. Ensuite, dans le package.json, copiez les scripts, la liste des navigateurs et les dépendances du projet React. Ajoutez également "homepage": "./" à la racine du package.json pour activer la compatibilité avec Cordova.

Une fois le package.json fusionné, nous voudrons modifier le fichier public/index.html pour qu'il fonctionne avec Cordova. Ouvrez le fichier et copiez les balises meta de www/index.html ainsi que le script à la fin de la balise body lorsque Cordova.js est chargé.

Ensuite, modifiez le fichier src/index.js pour détecter s'il s'exécute sur Cordova. S'il s'exécute sur Cordova, nous voudrons exécuter le code de rendu dans le gestionnaire d'événements deviceready. S'il s'exécute dans un navigateur standard, effectuez le rendu immédiatement.

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

Enfin, nous devons configurer notre pipeline de déploiement. Ajoutez la définition ci-dessous dans le fichier config.xml :

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

Et placez le script suivant dans 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); }); }); }); };

Cela exécute la construction React et place le dossier de construction à l'endroit approprié avant le démarrage de la construction Cordova, automatisant ainsi le processus de déploiement.

Maintenant, nous pouvons essayer d'exécuter notre application. Exécutez ce qui suit dans la ligne de commande :

 npm install rimraf npm install npm run start

Vous devriez voir l'application React configurée et exécutée dans le navigateur. Ajouter Cordoue maintenant :

 cordova platform add iOS cordova run iOS

Et vous devriez voir l'application React s'exécuter dans l'émulateur.

Configuration du routeur et des packages

Pour configurer une partie de l'infrastructure dont nous avons besoin pour créer l'application, commençons par installer les packages requis :

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

Nous allons maintenant configurer le routage, et ce faisant, nous allons également configurer un simple objet d'état global qui sera partagé par tous les composants. Dans une application de production, nous voudrons utiliser un système de gestion d'état comme Redux ou MobX, mais nous allons faire simple pour l'instant. Accédez à App.js et configurez la route ainsi :

 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> }

Avec cette définition de route, nous avons introduit deux routes que nous devrons implémenter : Input et Display. Notez que la variable curatedTweets est transmise à Display et que la variable setCuratedTweets est transmise à Input. Cela signifie que le composant d'entrée pourra appeler la fonction pour définir la variable curatedTweets , et Display obtiendra la variable à afficher.

Pour commencer à coder les composants, créons un dossier sous /src appelé /src/components. Sous /src/components, créez un autre dossier appelé /src/components/input et deux fichiers en dessous : input.js et input.css. Faites de même pour le composant Display - créez /src/components/display et en dessous : display.js et display.css.

Sous ceux-ci, créons des composants de stub, comme ceci :

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

Et pareil pour Display :

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

Avec cela, notre wireframing est terminé et l'application devrait fonctionner. Codons maintenant la page d'entrée.

Page d'entrée

Plan d'ensemble

Avant d'écrire le code, réfléchissons à ce que nous voulons que notre page d'entrée fasse. Évidemment, nous voudrons un moyen pour les utilisateurs de saisir et de modifier les poignées Twitter qu'ils souhaitent extraire. Nous voulons également que les utilisateurs puissent indiquer qu'ils ont terminé. Lorsque les utilisateurs indiquent qu'ils ont terminé, nous souhaitons extraire les tweets sélectionnés de notre API de curation Python et enfin accéder au composant Display.

Maintenant que nous savons ce que nous voulons que notre composant fasse, nous sommes prêts à coder.

Configuration du fichier

Commençons par importer la bibliothèque React Router withRouter pour accéder à la fonctionnalité de navigation, les composants React Bootstrap dont nous avons besoin, comme ceci :

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

Maintenant, définissons la fonction stub pour Input. Nous savons que Input obtient la fonction setCuratedTweets , et nous voulons également lui donner la possibilité de naviguer vers l'itinéraire d'affichage après avoir défini les tweets sélectionnés à partir de notre API Python. Par conséquent, nous voudrons prendre des props setCuratedTweets et history (pour la navigation).

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

Pour lui donner l'accès à l'API d'historique, nous l'envelopperons avec withRouter dans l'instruction d'exportation à la fin du fichier :

 export default withRouter(Input);

Conteneurs de données

Configurons les conteneurs de données à l'aide de React Hooks. Nous avons déjà importé le crochet useState afin de pouvoir ajouter le code suivant au corps du composant Input :

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

Cela crée le conteneur et les modificateurs pour les poignées, qui contiendront la liste des poignées que l'utilisateur souhaite extraire, et le handleText , qui contiendra le contenu de la zone de texte que l'utilisateur utilise pour saisir la poignée.

Maintenant, codons les composants de l'interface utilisateur.

Composants de l'interface utilisateur

Les composants de l'interface utilisateur seront assez simples. Nous aurons une ligne Bootstrap contenant la zone de texte d'entrée ainsi que deux boutons, un pour ajouter le contenu actuel de la zone de saisie à la liste des poignées et un pour extraire de l'API. Nous aurons une autre ligne Bootstrap qui affiche la liste des poignées que l'utilisateur souhaite extraire à l'aide du groupe de listes Bootstrap. En code, ça ressemble à ça :

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

En plus du composant d'interface utilisateur, nous souhaitons implémenter les trois gestionnaires d'événements d'interface utilisateur qui gèrent les modifications de données. Le gestionnaire d'événements getPull , qui appelle l'API, sera implémenté dans la section suivante.

 // 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); }

Nous sommes maintenant prêts à implémenter l'appel d'API.

Appel d'API

Pour l'appel d'API, nous voulons prendre les poignées que nous voulons extraire, les envoyer à l'API Python dans une requête POST et placer le résultat JSON résultant dans la variable curatedTweets . Ensuite, si tout se passe bien, nous souhaitons naviguer par programmation vers la route / display . Sinon, nous consignerons l'erreur dans la console afin de pouvoir déboguer plus facilement.

En mode code, cela ressemble à ceci :

 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); }) }

Et avec ça, nous devrions être prêts à partir. N'hésitez pas à exécuter l'application, à ajouter quelques poignées et à envoyer une demande à l'API.

Maintenant, nous sommes prêts à coder la page de sentiment.

Mode de tri des sentiments

Parce que l'API Python trie déjà les tweets par sentiment, une fois que nous avons le résultat de l'API Python, la page de sentiment n'est en fait pas trop difficile.

Plan d'ensemble

Nous voudrons une interface de liste pour afficher les tweets. Nous voudrons également que quelques composants de navigation passent en mode de regroupement de sujets et reviennent à la page d'entrée.

Pour commencer, définissons le sous-composant du mode SentimentDisplay dans le fichier display.js.

Composant SentimentDisplay

Le SentimentDisplay prendra l'objet curatedTweets et affichera les tweets triés par sentiment dans une liste. Avec l'aide de React-Bootstrap, le composant est assez simple :

 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> }

Pendant que nous y sommes, ajoutons également un peu de style. Mettez ce qui suit dans display.css et importez-le :

 .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; }

Nous pouvons maintenant afficher le composant SentimentDisplay. Modifiez la fonction Display comme suit :

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

Profitons-en également pour coder les composants de navigation. Nous aurons besoin de deux boutons - le bouton "Retour à l'édition" et le mode groupe de sujets.

Nous pouvons implémenter ces boutons dans une ligne Bootstrap séparée juste au-dessus du composant SentimentDisplay, comme ceci :

 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>

Exécutez l'application et extrayez les tweets de quelques poignées. Ça a l'air plutôt chouette !

Mode de regroupement de sujets

Maintenant, nous voulons implémenter le mode de regroupement de sujets. C'est un peu plus complexe que le SentimentDisplay, mais encore une fois, certains composants Bootstrap très pratiques nous aident énormément.

Plan d'ensemble

Nous obtiendrons toutes les phrases nominales et les afficherons sous forme de liste en accordéon. Nous rendrons ensuite les tweets contenant les phrases nominales une fois la liste d'accordéon développée.

Mise en œuvre du passage au mode de regroupement de rubriques

Tout d'abord, implémentons la logique pour passer du mode sentiment au mode groupement de sujets. Commençons par créer d'abord le composant stub :

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

Et définissez une logique pour créer un mode pour l'afficher. Dans le composant d'affichage principal, ajoutez les lignes suivantes pour créer la logique pour laquelle le composant est affiché.

 // 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'

Et modifiez le JSX comme suit pour ajouter la logique :

 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>

Maintenant, vous devriez voir le stub d'affichage du groupe de sujets lorsque vous basculez.

Le composant TopicDisplay

Nous sommes maintenant prêts à coder le composant TopicDisplay . Comme indiqué précédemment, il tirera parti de la liste d'accordéons Bootstrap. La mise en œuvre est en fait assez simple :

 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> }

Exécutez l'application et vous devriez voir l'affichage du sujet.

L'application est maintenant terminée et nous sommes prêts à créer l'application pour l'émulateur.

Exécution de l'application dans l'émulateur

Cordova facilite l'exécution de l'application dans l'émulateur. Exécutez simplement :

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

Et vous devriez voir l'application dans l'émulateur. Parce que Bootstrap est une application Web réactive, l'application Web s'adapte à la largeur d'un iPhone et tout est plutôt joli.

Une fois l'application Cordova terminée, examinons maintenant l'implémentation Ionic.

Implémentation de la réaction ionique

Amorce ionique

Ionic est une bibliothèque de composants Web et une boîte à outils CLI qui facilite la création d'applications hybrides. À l'origine, Ionic était construit sur AngularJS et Cordova, mais ils ont depuis publié leurs composants dans React.js et ont commencé à prendre en charge Capacitor, une plate-forme similaire à Cordova. Ce qui distingue Ionic, c'est que même si vous utilisez des composants Web, les composants ressemblent beaucoup à une interface mobile native. De plus, l'aspect et la convivialité des composants Ionic s'adaptent automatiquement au système d'exploitation sur lequel ils s'exécutent, ce qui aide à nouveau l'application à paraître et à se sentir plus native et naturelle. Enfin, bien que cela sorte du cadre de notre article, Ionic fournit également plusieurs outils de construction qui facilitent un peu le déploiement de votre application.

Pour notre application, nous utiliserons les composants React d'Ionic pour créer l'interface utilisateur tout en tirant parti d'une partie de la logique JavaScript que nous avons construite au cours de la section Cordova.

Configuration de l'application

Tout d'abord, nous voudrons installer les outils Ionic. Exécutons donc ce qui suit :

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

Et une fois l'installation terminée, allons dans le dossier du projet. Maintenant, nous utilisons la CLI Ionic pour créer notre nouveau dossier de projet :

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

Regardez la magie se produire, et allez maintenant dans le dossier avec :

 cd twitter-curation-Ionic

Et exécutez l'application vierge avec :

 ionic serve

Notre application est ainsi configurée et prête à fonctionner. Définissons quelques itinéraires.

Avant de poursuivre, vous remarquerez qu'Ionic a démarré le projet en utilisant TypeScript. Bien que je ne fasse pas tout mon possible pour utiliser TypeScript, il possède de très belles fonctionnalités et nous l'utiliserons pour cette implémentation.

Configuration du routeur

Pour cette implémentation, nous utiliserons trois routes - input, sentimentDisplay et topicDisplay . Nous le faisons parce que nous voulons profiter des fonctionnalités de transition et de navigation fournies par Ionic et parce que nous utilisons des composants Ionic, et les listes d'accordéon ne sont pas préemballées avec Ionic. Nous pouvons implémenter les nôtres, bien sûr, mais pour ce tutoriel, nous resterons avec les composants Ionic fournis.

Si vous naviguez vers App.tsx, vous devriez voir les itinéraires de base déjà définis.

Page d'entrée

Plan d'ensemble

Nous utiliserons une grande partie de la logique et du code similaires à ceux de l'implémentation Bootstrap, avec quelques différences clés. Premièrement, nous utiliserons TypeScript, ce qui signifie que nous aurons des annotations de type pour notre code, que vous verrez dans la section suivante. Deuxièmement, nous utiliserons des composants Ionic, dont le style est très similaire à Bootstrap mais dont le style sera sensible au système d'exploitation. Enfin, nous naviguerons dynamiquement en utilisant l'API d'historique comme dans la version Bootstrap mais en accédant à l'historique légèrement différemment en raison de l'implémentation du routeur ionique.

Mise en place

Commençons par configurer le composant d'entrée avec un composant stub. Créez un dossier sous les pages nommées input et créez sous celui-ci un fichier nommé Input.tsx. Dans ce fichier, placez le code suivant pour créer un composant React. Notez que parce que nous utilisons TypeScript, c'est un peu différent.

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

Et changez le composant dans App.tsx en :

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

Maintenant, lorsque vous actualisez l'application, vous devriez voir le composant Input stub.

Conteneurs de données

Créons maintenant les conteneurs de données. Nous voulons les conteneurs pour les poignées Twitter saisies ainsi que le contenu actuel de la zone de saisie. Comme nous utilisons TypeScript, nous devrons ajouter les annotations de type à notre invocation useState dans la fonction du composant :

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

Nous voudrons également un conteneur de données pour contenir les valeurs de retour de l'API. Parce que le contenu de cela doit être partagé avec les autres itinéraires, nous les définissons au niveau App.tsx. Importez useState depuis React dans le fichier App.tsx et modifiez la fonction de conteneur d'application comme suit :

 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> ); }

À ce stade, si vous utilisez un éditeur avec coloration syntaxique comme Visual Studio Code, vous devriez voir les CuratedTweets s'allumer. En effet, le fichier ne sait pas à quoi ressemble l'interface CuratedTweets. Définissons cela maintenant. Créez un dossier sous src appelé interfaces et créez un fichier à l'intérieur appelé CuratedTweets.tsx. Dans le fichier, définissez l'interface CuratedTweets comme suit :

 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> }

L'application connaît désormais la structure des données de retour de l'API. Importez l'interface CuratedTweets dans App.tsx. Vous devriez voir la compilation App.tsx sans problème maintenant.

Nous devons faire quelques choses de plus ici. Nous devons transmettre la fonction setCuratedTweets au composant Input et rendre le composant Input conscient de cette fonction.

Dans l'App.tsx, modifiez la route d'entrée comme suit :

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

Maintenant, vous devriez voir l'éditeur signaler quelque chose d'autre - Input ne sait pas que le nouvel accessoire lui est transmis, nous voudrons donc le définir dans 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); }) }

Ajout d'un en-tête

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>

Maintenant ça a l'air sympa !

Page triée par sentiment

Plan d'ensemble

La page triée par sentiment sera en grande partie similaire à la page triée par sentiment de la page Bootstrap mais en utilisant TypeScript et Ionic. Nous implémenterons également l'affichage de sujet en tant qu'itinéraire séparé pour tirer parti des fonctionnalités de navigation d'Ionic lors de l'exécution sur mobile, nous devrons donc donner à cette page la possibilité de naviguer également vers l'itinéraire d'affichage de sujet.

Configuration de l'itinéraire

Commençons par créer un nouveau dossier appelé sentimentsorted et un fichier nommé SentimentSorted.tsx en dessous. Exportez un composant de stub comme ceci :

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

Et dans l'App.tsx, importez le composant :

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

Et ajoutez l'itinéraire :

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

Vous obtiendrez une erreur TypeScript indiquant que SentimentSorted n'attend pas les props curatedTweets, alors prenons soin de cela maintenant dans la section suivante.

Composants de l'interface utilisateur

Commençons par définir les accessoires du conteneur. Tout comme le composant d'entrée :

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

Et maintenant, changez l'affichage du stub :

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

Et tout devrait compiler.

L'affichage est très simple, c'est juste une IonList avec des composants d'affichage :

 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>

Si vous enregistrez et récupérez des tweets à l'aide du composant d'entrée, vous devriez voir les tweets affichés dans une liste.

Maintenant, ajoutons les boutons de navigation. Ajouter à l'IonGrid :

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

Le switchToInput est très simple à implémenter avec l'API history :

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

Et ToggleDisplayType devrait également être familier :

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

Nous avons maintenant le composant SentimentDisplay implémenté. Maintenant, avant d'implémenter la page d'affichage des sujets, nous devons implémenter le composant qui affiche tous les sujets. Nous le ferons dans la section suivante.

Composant Groupes de sujets

Ajoutons une option d'affichage de la liste des sujets et affichons-la conditionnellement. Pour ce faire, nous devons diviser la liste d'affichage des sentiments. Renommez SentimentDisplay en Display, et décomposons la liste d'affichage des sentiments :

 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> }

Remarquez comment nous utilisons l'une des définitions de classe dans l'interface CuratedTweets. En effet, ces composants n'ont pas besoin de l'intégralité de l'objet CuratedTweets, mais uniquement d'un sous-ensemble. La liste des sujets est très similaire :

 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> }

Et maintenant, l'affichage conditionnel est facile à configurer dans le composant d'affichage :

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

Assurez-vous de modifier l'exportation par défaut, et nous sommes maintenant prêts à implémenter la page d'affichage de sujet.

Page d'affichage du sujet

Plan d'ensemble

La page d'affichage des sujets est un affichage de liste similaire à l'affichage des sentiments, mais nous rechercherons le sujet en question à partir du paramètre route.

Configuration de l'itinéraire

Si vous êtes arrivé jusqu'ici, vous devriez déjà savoir quoi faire. Créons un dossier de page appelé topicdisplay et un TopicDisplay.tsx, écrivons un composant stub et importons-le dans la page App.tsx. Maintenant, configurons les routes :

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

Nous sommes maintenant prêts à implémenter le composant d'interface utilisateur.

Composants de l'interface utilisateur

Commençons par créer la définition de ContainerProps :

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

Maintenant, nous devrons récupérer le sujet à partir du nom du chemin de l'URL. Pour ce faire, nous utiliserons l'API d'historique. Importons donc useHistory , instancions l'API d'historique et extrayons le sujet du chemin d'accès. Pendant que nous y sommes, implémentons également la fonctionnalité de retour en arrière :

 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];

Maintenant que nous avons les tweets avec ce sujet particulier, l'affichage est en fait assez simple :

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

Sauvegardez et exécutez, et tout devrait bien se passer.

Exécution de l'application dans l'émulateur

Pour exécuter l'application dans l'émulateur, nous exécutons simplement quelques commandes ioniques pour ajouter la plate-forme mobile et copier le code, de la même manière que nous configurons les choses avec 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

Et vous devriez voir l'application apparaître.

Implémentation native de React

React Native Primer

React Native adopte une approche très différente des approches Web des sections précédentes. React Native rend votre code React sous forme de composants natifs. Cela vient avec plusieurs avantages. Premièrement, l'intégration avec le système d'exploitation sous-jacent est beaucoup plus profonde, ce qui permet au développeur de tirer parti des nouvelles fonctionnalités du smartphone et des fonctionnalités spécifiques au système d'exploitation qui peuvent ne pas être disponibles via Cordova/Capacitor. Deuxièmement, comme il n'y a pas de moteur de rendu Web au milieu, une application React Native est généralement plus rapide qu'une application écrite avec Cordova. Enfin, comme React Native permet l'intégration de composants natifs, les développeurs peuvent exercer un contrôle beaucoup plus précis sur leur application.

Pour notre application, nous utiliserons la logique des sections précédentes et utiliserons une bibliothèque de composants React Native appelée NativeBase pour coder notre interface utilisateur.

Configuration de l'application

Tout d'abord, vous voudrez installer tous les composants requis de React Native en suivant les instructions ici.

Une fois React Native installé, commençons le projet :

 react-native init TwitterCurationRN

Laissez le script d'installation s'exécuter et, éventuellement, le dossier devrait être créé. Cd dans le dossier et exécutez run-ios natifs réactifs, et vous devriez voir l'émulateur apparaître avec l'exemple d'application.

Nous voudrons également installer NativeBase puisque c'est notre bibliothèque de composants. Pour ce faire, nous exécutons :

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

Nous souhaitons également installer le navigateur de pile React Native. Courons :

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

Et

 react-native link cd ios pod-install cd

Pour terminer la liaison et l'installation des plugins natifs.

Configuration du routeur

Pour le routage, nous utiliserons le navigateur de pile que nous avons installé à l'étape ci-dessus.

Importez les composants du routeur :

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

Et maintenant, nous créons un navigateur de pile :

 const Stack = createStackNavigator();

Modifiez le contenu du composant App pour utiliser le navigateur de pile :

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

À ce stade, vous obtiendrez une erreur car l'entrée n'est pas encore définie. Définissons un élément stub juste pour le rendre heureux.

Créez un dossier de composants dans votre projet, créez un fichier nommé Entry.jsx et ajoutez un composant stub comme ceci :

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

Importez le composant Entry dans votre application et il devrait être généré.

Maintenant, nous sommes prêts à coder la page d'entrée.

Page d'entrée

Plan d'ensemble

Nous allons implémenter une page très similaire à celle implémentée ci-dessus mais en utilisant des composants NativeBase. La plupart des API JavaScript et React que nous avons utilisées, telles que les crochets et la récupération, sont toujours disponibles.

La seule différence sera la façon dont nous travaillons avec l'API de navigation, que vous verrez plus tard.

Composants de l'interface utilisateur

Les composants NativeBase supplémentaires que nous utiliserons sont Container, Content, Input, List, ListItem et Button. Ceux-ci ont tous des analogues dans Ionic et Bootstrap React, et les constructeurs de NativeBase l'ont rendu très intuitif pour les personnes familiarisées avec ces bibliothèques. Importez simplement comme ceci:

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

Et le composant est :

 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>

Et maintenant, implémentons les gestionnaires d'état et d'événement :

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

Et enfin, l'appel 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); }) }

Notez que cette implémentation est la même que dans l'implémentation NativeBase, sauf que nous naviguons d'une manière différente. Le navigateur de pile transmet à ses composants un accessoire appelé "navigation" et qui peut être utilisé pour naviguer entre les routes avec .navigate . En plus de simplement naviguer, vous pouvez également transmettre des données au composant cible. Nous allons utiliser ce mécanisme pour transmettre les données.

Pour que l'application soit compilée, nous devons encore rendre Entry conscient du composant de navigation. Pour ce faire, nous devrons modifier la déclaration de la fonction du composant :

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

Maintenant, enregistrez et vous devriez voir la page.

Page triée par sentiment

Plan d'ensemble

Nous allons implémenter la page de sentiment un peu comme les sections précédentes, mais nous allons styliser la page un peu différemment, et nous utiliserons également la bibliothèque de navigation différemment pour obtenir la valeur de retour de l'appel API.

Parce que React Native n'a pas de CSS, nous devrons soit définir un objet StyleSheet, soit simplement coder les styles en ligne. Étant donné que nous allons partager une partie du style entre les composants, créons une feuille de style globale. Nous le ferons après la configuration de l'itinéraire.

De plus, étant donné que StackNavigator dispose d'un bouton de navigation Retour intégré, nous n'aurons pas besoin d'implémenter notre propre bouton Retour.

Configuration de l'itinéraire

La définition de route dans StackNavigator est très simple. Nous en créons simplement un nouveau nommé Stack Screen et lui donnons le composant, un peu comme le routeur React.

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

Pour que cela fonctionne, nous devrons bien sûr créer un composant stub dans components/SentimentDisplay.js :

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

Et importez-le :

 import SentimentDisplay from './components/SentimentDisplay';

Maintenant, nous sommes prêts à créer la feuille de style globale.

Feuille de style globale

Tout d'abord, créez un fichier nommé globalStyles.js. Ensuite, importez le composant StyleSheet depuis React Native et définissez les styles :

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

Et nous sommes prêts à coder l'interface utilisateur.

Composants de l'interface utilisateur

Le composant d'interface utilisateur est assez familier, à l'exception de la façon dont nous travaillons avec les routes. Nous voudrons utiliser la navigation et la route des accessoires spéciaux de StackNavigator pour obtenir l'état actuel de l'application et pour accéder à l'affichage du sujet si l'utilisateur souhaite voir cette page.

Modifiez la définition du composant pour accéder aux accessoires de navigation :

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

Et maintenant, nous implémentons la lecture de l'état de l'application et la fonctionnalité de navigation :

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

Importez les styles globaux :

 import globalStyles from './globalStyles';

Et les composants :

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

Et enfin, les composants :

 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>;

Enregistrez et essayez d'extraire des tweets, et vous devriez voir l'affichage du sentiment. Passons maintenant à la page de regroupement de sujets.

Page de regroupement de rubriques

Plan d'ensemble

L'affichage de sujet est à nouveau très similaire. Nous utiliserons un générateur de gestionnaire pour créer une fonction de navigation permettant d'accéder à la page d'affichage d'un élément de sujet spécifique, et nous définirons également une feuille de style spécifique à cette page.

Une nouvelle chose que nous allons faire est d'implémenter un TouchableOpacity, qui est un composant spécifique à React Native qui fonctionne un peu comme un bouton.

Configuration de l'itinéraire

La définition de la route est la même qu'avant :

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

Le composant stub components/TopicDisplay.js :

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

Et importez-le :

 import TopicDisplay from './components/TopicDisplay;

Composants de l'interface utilisateur

Beaucoup de cela vous semblera très familier. Importez les fonctions de la bibliothèque :

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

Importez les styles globaux :

 import globalStyles from './globalStyles';

Définissez les styles personnalisés :

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

Définissez les accessoires de navigation :

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

Définissez les gestionnaires de données et d'action. Notez que nous utilisons un générateur de gestionnaire, une fonction qui renvoie une fonction :

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

Et maintenant, les composants. Notez que nous utilisons un TouchableOpacity, qui peut avoir un gestionnaire onPress . Nous aurions également pu utiliser TouchableTransparency, mais l'animation cliquer-maintenir de TouchableOpacity était mieux adaptée à notre application.

 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>;

Et cela devrait le faire. Enregistrez et essayez l'application !

Maintenant, sur la page des éléments d'affichage du sujet.

Rubrique Afficher l'élément Page

Plan d'ensemble

La page de l'élément d'affichage du sujet est très similaire et toutes les idiosyncrasies sont prises en charge dans les autres sections, il devrait donc être facile de naviguer à partir d'ici.

Configuration de l'itinéraire

Nous allons ajouter la définition de route :

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

Ajoutez l'importation :

 import TopicDisplayItem from './components/TopicDisplayItem';

Et créez le composant stub. Au lieu d'un simple composant, importons également les composants NativeBase que nous utiliserons et définissons les accessoires de route :

 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;

Composants de l'interface utilisateur

Le composant d'interface utilisateur est assez simple. Nous l'avons déjà vu et nous n'implémentons pas vraiment de logique personnalisée. Alors, allons-y ! Respirez profondément…

 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>;

Économisez, et nous devrions être prêts à partir ! Nous sommes maintenant prêts à exécuter l'application dans l'émulateur… attendez, n'est-ce pas déjà ?

Exécution de l'application

Eh bien, puisque vous travaillez avec React Native, vous exécutez déjà l'application dans l'émulateur, donc cette partie est déjà prise en charge. C'est l'une des grandes choses à propos de l'environnement de développement de React Native.

Ouf! Avec cela, nous en avons terminé avec la partie codage de cet article. Voyons ce que nous avons appris sur les technologies.

Comparer les technologies

Cordoue : avantages et inconvénients

La meilleure chose à propos de Cordova est la vitesse à laquelle un développeur Web qualifié peut coder quelque chose de fonctionnel et raisonnablement présentable. Les compétences et l'expérience en développement Web se transfèrent facilement car, après tout, vous codez une application Web. Le processus de développement est simple et rapide, et l'accès à l'API Cordova est également simple et intuitif.

Les inconvénients de l'utilisation directe de Cordova proviennent principalement de la dépendance excessive aux composants Web. Les utilisateurs s'attendent à une expérience utilisateur et à une conception d'interface spécifiques lorsqu'ils utilisent des applications mobiles, et lorsqu'une application ressemble à un site mobile, l'expérience peut être un peu choquante. De plus, la plupart des fonctionnalités intégrées aux applications, telles que les animations de transition et les utilitaires de navigation, doivent être implémentées manuellement.

Ionique : avantages et inconvénients

La meilleure partie d'Ionic était le nombre de fonctionnalités centrées sur le mobile que j'ai obtenues "gratuitement". En codant un peu comme j'aurais codé une application Web, j'ai pu créer une application qui semble beaucoup plus adaptée aux mobiles que d'utiliser simplement Cordova et React-Bootstrap. Il y avait une animation de navigation, des boutons avec des styles natifs et de nombreuses options d'interface utilisateur qui rendaient l'expérience utilisateur très fluide.

L'inconvénient de l'utilisation d'Ionic était en partie causé par ses atouts. Tout d'abord, il était parfois difficile d'imaginer comment l'application se comporterait dans divers environnements. Ce n'est pas parce que l'application avait l'air d'un côté que le même placement de l'interface utilisateur aurait le même aspect dans un autre environnement. Deuxièmement, Ionic repose sur de nombreuses technologies sous-jacentes, et l'accès à certains composants s'est avéré difficile. Enfin, cela est spécifique à Ionic-React, mais comme Ionic a d'abord été conçu pour Angular, de nombreuses fonctionnalités Ionic-React semblaient avoir moins de documentation et de support. Cependant, l'équipe Ionic semble très attentive aux besoins des développeurs React et livre rapidement de nouvelles fonctionnalités.

Réagir natif : avantages et inconvénients

React Native a eu une expérience utilisateur très fluide en se développant sur mobile. En se connectant directement à l'émulateur, l'apparence de l'application n'était pas un mystère. L'interface de débogage basée sur le Web a été extrêmement utile pour l'application croisée des techniques de débogage du monde des applications Web, et l'écosystème est assez robuste.

L'inconvénient de React Native vient de sa proximité avec l'interface native. De nombreuses bibliothèques basées sur DOM ne pouvaient pas être utilisées, ce qui signifiait qu'il fallait apprendre de nouvelles bibliothèques et les meilleures pratiques. Sans l'avantage du CSS, le style de l'application était un peu moins intuitif. Enfin, avec de nombreux nouveaux composants à apprendre (par exemple, View au lieu de div, Text component enveloppant tout, Buttons vs. TouchableOpacity vs. TouchableTransparency, etc.), il y a un peu de courbe d'apprentissage au début si quelqu'un entre dans le React Monde natif avec peu de connaissances préalables sur la mécanique.

Quand utiliser chaque technologie

Parce que Cordova, Ionic et React Native ont tous des avantages et des inconvénients très importants, chaque technologie a un contexte dans lequel elle bénéficierait de la meilleure productivité et des meilleures performances.

Si vous avez déjà une application existante qui est d'abord Web avec une forte identité de marque entourant la conception de l'interface utilisateur et l'apparence générale, votre meilleure option serait Cordova, qui vous donne accès aux fonctionnalités natives du smartphone tout en vous permettant de réutiliser la plupart de vos composants Web et préservez l'identité de votre marque dans le processus. Pour les applications relativement simples utilisant un cadre réactif, vous pourrez peut-être créer une application mobile avec très peu de modifications nécessaires. Cependant, votre application ressemblera moins à une application qu'à une page Web, et certains des composants que les gens attendent d'une application mobile seront codés séparément. Par conséquent, je recommande Cordova dans les cas où vous êtes dans un projet Web d'abord portant l'application sur mobile.

Si vous commencez à coder une nouvelle application avec une philosophie axée sur l'application, mais que les compétences de votre équipe concernent principalement le développement Web, je vous recommande Ionic. La bibliothèque d'Ionic vous permet d'écrire rapidement du code qui ressemble et se sent proche des composants natifs tout en vous permettant d'appliquer vos compétences et votre instinct en tant que développeur Web. J'ai trouvé que les meilleures pratiques de développement Web s'appliquaient facilement au développement avec Ionic, et que le style avec CSS fonctionnait de manière transparente. De plus, la version mobile du site semble beaucoup plus native qu'un site Web codé à l'aide d'un framework CSS réactif. Cependant, à certaines étapes du processus, j'ai constaté que l'intégration de l'API React-Ionic-Native nécessitait des ajustements manuels, ce qui pouvait prendre du temps. Par conséquent, je recommande Ionic dans les cas où votre application est développée à partir de zéro et que vous souhaitez partager une quantité importante de code entre une application Web mobile et une application mobile.

Si vous codez une nouvelle application avec une base de code native implémentée, vous pouvez essayer React Native. Même si vous n'utilisez pas de code natif, cela pourrait également être la meilleure option dans les cas où vous connaissez déjà React Native, ou lorsque votre principale préoccupation est l'application mobile plutôt qu'une application hybride. Ayant concentré la plupart de mes efforts de développement front-end sur le développement Web, j'ai d'abord constaté que commencer avec React Native avait plus d'une courbe d'apprentissage qu'Ionic ou Cordova en raison des différences dans l'organisation des composants et les conventions de codage. Cependant, une fois ces nuances apprises, l'expérience de codage est assez fluide, en particulier avec l'aide d'une bibliothèque de composants comme NativeBase. Compte tenu de la qualité de l'environnement de développement et du contrôle de l'application, si le front-end de votre projet est principalement une application mobile, je recommanderais React Native comme outil de choix.

Sujets futurs

L'un des sujets que je n'ai pas eu le temps d'explorer était la facilité d'accès aux API natives telles que la caméra, la géolocalisation ou l'authentification biométrique. L'un des grands avantages du développement mobile est l'accessibilité d'un riche écosystème d'API qui n'est généralement pas accessible sur le navigateur.

Dans les prochains articles, j'aimerais explorer la facilité de développement de ces applications natives compatibles API à l'aide de diverses technologies multiplateformes.

Conclusion

Aujourd'hui, nous avons mis en place une application de curation Twitter utilisant trois technologies de développement mobile multiplateformes différentes. J'espère que cela vous a donné une bonne idée de ce à quoi ressemble chaque technologie et vous a inspiré à développer votre propre application basée sur React.

Merci pour la lecture!