NLP de pila completa con React: Ionic vs Cordova vs React Native
Publicado: 2022-03-11En los aproximadamente 15 años transcurridos desde que Apple lanzó el primer iPhone, el panorama del desarrollo de software ha cambiado drásticamente. A medida que los teléfonos inteligentes obtienen una adopción generalizada y continúan creciendo en capacidades únicas, los usuarios prefieren cada vez más acceder a los servicios de software a través de dispositivos móviles en lugar de computadoras de escritorio o portátiles. Los teléfonos inteligentes ofrecen funciones como geolocalización, autenticación biométrica y detección de movimiento, muchas de las cuales las plataformas de escritorio recién ahora están comenzando a copiar. En algunos datos demográficos, un teléfono inteligente o un dispositivo móvil similar es el principal medio de consumo de software, sin pasar por las computadoras por completo.
Las empresas han notado este cambio y lo han reforzado de manera importante. Las aplicaciones móviles ya no son una ocurrencia tardía. Las aplicaciones que van desde Robinhood, una empresa de corretaje financiero, hasta Instagram, una empresa de redes sociales, y Uber, una empresa de transporte privado, están adoptando una estrategia de desarrollo que prioriza los dispositivos móviles. Si hay una aplicación de escritorio, a menudo se ofrece como un complemento de la aplicación móvil, en lugar del enfoque principal.
Para los desarrolladores full-stack, es crucial adaptarse a estas tendencias cambiantes. Afortunadamente, existen muchas tecnologías maduras y bien respaldadas disponibles para ayudar a los desarrolladores web a aplicar sus habilidades al desarrollo móvil. Hoy exploraremos tres de estas tecnologías: Cordova, Ionic y React Native. Usaremos React.js, uno de los marcos más populares para el desarrollo web front-end, como nuestra tecnología de desarrollo central. Si bien nos centraremos en desarrollar una aplicación para iPhone, estas son tecnologías multiplataforma y se podrán compilar de forma cruzada con la plataforma Android.
Lo que construiremos hoy
Construiremos una aplicación que utilice procesamiento de lenguaje natural (NLP, por sus siglas en inglés) para procesar y seleccionar feeds de Twitter. La aplicación permitirá al usuario seleccionar un conjunto de identificadores de Twitter, extraer las actualizaciones más recientes utilizando una API de Twitter y categorizar los tweets según el sentimiento y el tema. Luego, el usuario podrá ver los tweets según el sentimiento o el tema.
Parte trasera
Antes de construir el front-end, querremos construir el back-end. Mantendremos el back-end simple por ahora: usaremos un análisis de sentimiento básico y listo para usar y etiquetado de parte del discurso, junto con un poco de limpieza de datos para tratar problemas específicos del conjunto de datos. Usaremos una biblioteca NLP de código abierto llamada TextBlob y publicaremos el resultado en Flask.
Análisis de sentimiento, etiquetado de partes del discurso y PNL: una introducción rápida
Si no ha trabajado antes con aplicaciones de análisis de lenguaje natural, estos términos pueden resultarle muy extraños. NLP es un término general para tecnologías que analizan y procesan datos del lenguaje humano natural. Si bien este es un tema amplio, existen muchos desafíos que son comunes a todas las tecnologías que abordan esta área. Por ejemplo, el lenguaje humano, a diferencia del lenguaje de programación o de los datos numéricos, tiende a estar poco estructurado debido a la naturaleza permisiva de la gramática del lenguaje humano. Además, el lenguaje humano tiende a ser extremadamente contextual, y una frase pronunciada o escrita en un contexto puede no traducirse a otro contexto. Finalmente, dejando de lado la estructura y el contexto, el lenguaje es extremadamente complejo. Las palabras más abajo en un párrafo pueden cambiar el significado de una oración al principio del párrafo. El vocabulario se puede inventar, redefinir o cambiar. Todas estas complejidades hacen que las técnicas de análisis de datos sean difíciles de aplicar de forma cruzada.
El análisis de sentimientos es un subcampo de PNL que se enfoca en comprender la emotividad de un pasaje de lenguaje natural. Si bien la emoción humana es inherentemente subjetiva y, por lo tanto, difícil de precisar tecnológicamente, el análisis de sentimientos es un subcampo que tiene una inmensa promesa comercial. Algunas aplicaciones del análisis de sentimientos incluyen la clasificación de reseñas de productos para identificar evaluaciones positivas y negativas de varias funciones, detectar el estado de ánimo de un correo electrónico o un discurso y agrupar letras de canciones por estado de ánimo. Si está buscando una explicación más detallada de Análisis de sentimiento, puede leer mi artículo sobre la creación de una aplicación basada en Análisis de sentimiento aquí.
El etiquetado de parte del discurso, o etiquetado POS, es un subcampo muy diferente. El objetivo del etiquetado POS es identificar la parte del discurso de una palabra dada en una oración utilizando información gramatical y contextual. Identificar esta relación es una tarea mucho más difícil de lo que parece inicialmente: una palabra puede tener partes del discurso muy diferentes según el contexto y la estructura de la oración, y las reglas no siempre son claras incluso para los humanos. Afortunadamente, muchos modelos disponibles en la actualidad ofrecen modelos potentes y versátiles integrados con la mayoría de los principales lenguajes de programación. Si desea obtener más información, puede leer mi artículo sobre el etiquetado de POS aquí.
Matraz, TextBlob y Tweepy
Para nuestro back-end de NLP, usaremos Flask, TextBlob y Tweepy. Usaremos Flask para construir un servidor pequeño y liviano, TextBlob para ejecutar nuestro procesamiento de lenguaje natural y Tweepy para obtener tweets de la API de Twitter. Antes de comenzar a codificar, también querrá obtener una clave de desarrollador de Twitter para poder recuperar tweets.
Podemos escribir un back-end mucho más sofisticado y usar tecnologías NLP más complejas, pero para nuestros propósitos de hoy, mantendremos el back-end lo más simple posible.
Código de fondo
Ahora, estamos listos para comenzar a codificar. Encienda su editor y terminal de Python favoritos, ¡y empecemos!
Primero, querremos instalar los paquetes necesarios.
pip install flask flask-cors textblob tweepy python -m textblob.download_corpora
Ahora, escribamos el código para nuestra funcionalidad.
Abra un nuevo script de Python, llámelo server.py e importe las bibliotecas necesarias:
import tweepy from textblob import TextBlob from collections import defaultdict
Ahora escribamos algunas funciones auxiliares:
# 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
Ahora que tenemos escritas las funciones auxiliares, podemos juntar todo con un par de funciones 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
Ahora puede ejecutar la función download_analyze_tweets
en una lista de identificadores que desea seguir, y debería ver los resultados.
Ejecuté el siguiente código:
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)
Ejecutar esto produjo los siguientes resultados. Los resultados obviamente dependen del tiempo, por lo que si ve algo similar, está en el camino correcto.
[{'account': '@spacex', 'tweet': 'Falcon 9… [{'account': '@nasa', 'tweet': 'Our Mars rove… {'Falcon': [{'account': '@spacex', 'tweet': 'Falc….
Ahora podemos construir el servidor Flask, que es bastante simple. Cree un archivo vacío llamado server.py y escriba el siguiente código:
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)
Ejecute el servidor, y ahora debería poder enviar una solicitud de publicación al servidor utilizando un cliente HTTP de su elección. Pase {accounts: [“@NASA”, “@SpaceX”]} como un argumento json, y debería ver que la API devuelve algo similar a lo que se devolvió en el código de análisis de Twitter.
Ahora que tenemos un servidor, estamos listos para codificar el front-end. Debido a un pequeño matiz en la creación de redes a través de un emulador de teléfono, le recomiendo que implemente su API en algún lugar. De lo contrario, querrá detectar si su aplicación se está ejecutando en un emulador y enviar solicitudes a <Your Computer IP>:5000
en lugar de localhost:5000
cuando está en un emulador. Si implementa el código, simplemente puede enviar la solicitud a esa URL.
Hay muchas opciones para implementar el servidor. Para un servidor de depuración simple y gratuito con una configuración mínima requerida, recomiendo algo como PythonAnywhere, que debería poder ejecutar este servidor de forma inmediata.
Ahora que hemos codificado el servidor back-end, veamos el front-end. Comenzaremos con una de las opciones más convenientes para un desarrollador web: Cordova.
Implementación de Apache Cordova
Cartilla de Córdoba
Apache Cordova es una tecnología de software para ayudar a los desarrolladores web a apuntar a plataformas móviles. Al aprovechar las capacidades del navegador web implementadas en las plataformas de teléfonos inteligentes, Cordova envuelve el código de la aplicación web en un contenedor de aplicación nativo para crear una aplicación. Sin embargo, Cordova no es simplemente un navegador web sofisticado. A través de la API de Cordova, los desarrolladores web pueden acceder a muchas funciones específicas de los teléfonos inteligentes, como soporte fuera de línea, servicios de ubicación y cámara en el dispositivo.
Para nuestra aplicación, escribiremos una aplicación utilizando React.js como marco JS y React-Bootstrap como marco CSS. Debido a que Bootstrap es un marco CSS receptivo, ya tiene soporte para ejecutarse en pantallas más pequeñas. Una vez que la aplicación esté escrita, la compilaremos en una aplicación web usando Cordova.
Configuración de la aplicación
Comenzaremos haciendo algo único para configurar la aplicación Cordova React. En un artículo de Medium , el desarrollador Shubham Patil explica lo que estamos haciendo. Esencialmente, estamos configurando un entorno de desarrollo de React usando la CLI de React, y luego un entorno de desarrollo de Cordova usando la CLI de Cordova, antes de finalmente fusionar los dos.
Para comenzar, ejecute los siguientes dos comandos en su carpeta de código:
cordova create TwitterCurationCordova create-react-app twittercurationreact
Una vez finalizada la configuración, querremos mover el contenido de la carpeta pública y src de la aplicación React a la aplicación Cordova. Luego, en el paquete.json, copie los scripts, la lista de navegadores y las dependencias del proyecto React. También agregue "homepage": "./"
en la raíz del paquete.json para habilitar la compatibilidad con Cordova.
Una vez que se fusione el paquete.json, querremos cambiar el archivo public/index.html para que funcione con Cordova. Abra el archivo y copie las etiquetas meta de www/index.html, así como la secuencia de comandos al final de la etiqueta del cuerpo cuando se carga Cordova.js.
A continuación, cambie el archivo src/index.js para detectar si se está ejecutando en Cordova. Si se está ejecutando en Cordova, querremos ejecutar el código de procesamiento dentro del controlador de eventos deviceready. Si se está ejecutando en un navegador normal, simplemente renderice de inmediato.
const renderReactDom = () => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.cordova) { document.addEventListener('deviceready', () => { renderReactDom(); }, false); } else { renderReactDom(); }
Finalmente, necesitamos configurar nuestra canalización de implementación. Agregue la siguiente definición en el archivo config.xml:
<hook type="before_prepare" src="hooks/prebuild.js" />
Y coloque el siguiente script en 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); }); }); }); };
Esto ejecuta la compilación de React y coloca la carpeta de compilación en el lugar apropiado antes de que comience la compilación de Cordova, lo que automatiza el proceso de implementación.
Ahora, podemos intentar ejecutar nuestra aplicación. Ejecute lo siguiente en la línea de comando:
npm install rimraf npm install npm run start
Debería ver la aplicación React configurada y ejecutándose en el navegador. Añade Córdoba ahora:
cordova platform add iOS cordova run iOS
Y debería ver la aplicación React ejecutándose en el emulador.
Configuración de enrutador y paquetes
Para configurar parte de la infraestructura que necesitamos para construir la aplicación, comencemos instalando los paquetes necesarios:
npm install react-bootstrap react-router react-router-dom
Ahora configuraremos el enrutamiento y, mientras lo hacemos, también configuraremos un objeto de estado global simple que será compartido por todos los componentes. En una aplicación de producción, querremos usar un sistema de administración de estado como Redux o MobX, pero lo mantendremos simple por ahora. Vaya a App.js y configure la ruta así:
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 esta definición de ruta, hemos introducido dos rutas que necesitaremos implementar: Entrada y Visualización. Observe que la variable curatedTweets
se pasa a Display y la variable setCuratedTweets
se pasa a Input. Esto significa que el componente de entrada podrá llamar a la función para configurar la variable seleccionada de curatedTweets
, y Display obtendrá la variable para mostrar.
Para comenzar a codificar los componentes, creemos una carpeta en /src llamada /src/components. En /src/components, cree otra carpeta llamada /src/components/input y dos archivos debajo: input.js y input.css. Haga lo mismo para el componente Display: cree /src/components/display y debajo: display.js y display.css.
Debajo de ellos, creemos componentes de código auxiliar, así:
import React from 'react'; import 'input.css' const Input = () => <div>Input</div>; export default Input
Y lo mismo para la pantalla:
import React from 'react'; import display.css' const Display = () => <div>Display</div>; export default Display
Con eso, nuestro wireframing está completo y la aplicación debería ejecutarse. Codifiquemos ahora la página de entrada.
Página de entrada
Plan general
Antes de escribir el código, pensemos qué queremos que haga nuestra página de entrada. Obviamente, querremos una forma para que los usuarios ingresen y editen los identificadores de Twitter de los que quieren extraer. También queremos que los usuarios puedan indicar que han terminado. Cuando los usuarios indiquen que han terminado, querremos extraer los tweets seleccionados de nuestra API de selección de Python y finalmente navegar hasta el componente Pantalla.
Ahora que sabemos lo que queremos que haga nuestro componente, estamos listos para codificar.
Configuración del archivo
Comencemos importando la biblioteca de React Router withRouter
para obtener acceso a la funcionalidad de navegación, los componentes de React Bootstrap que necesitamos, así:
import React, {useState} from 'react'; import {withRouter} from 'react-router-dom'; import {ListGroup, Button, Form, Container, Row, Col} from 'react-bootstrap'; import './input.css';
Ahora, definamos la función stub para Input. Sabemos que Input obtiene la función setCuratedTweets
y también queremos darle la capacidad de navegar a la ruta de visualización después de configurar los tweets seleccionados de nuestra API de Python. Por lo tanto, querremos tomar de los props setCuratedTweets
y el historial (para la navegación).
const Input = ({setCuratedTweets, history}) => { return <div>Input</div> }
Para darle acceso a la API de historial, lo envolveremos con withRouter
en la declaración de exportación al final del archivo:
export default withRouter(Input);
Contenedores de datos
Configuremos los contenedores de datos usando React Hooks. Ya importamos el useState
para que podamos agregar el siguiente código al cuerpo del componente Input:
const [handles, setHandles] = useState([]); const [handleText, setHandleText] = useState('');
Esto crea el contenedor y los modificadores para identificadores, que contendrán la lista de identificadores de los que el usuario desea extraer, y handleText
, que contendrá el contenido del cuadro de texto que el usuario utiliza para ingresar el identificador.
Ahora, codifiquemos los componentes de la interfaz de usuario.
Componentes de la interfaz de usuario
Los componentes de la interfaz de usuario serán bastante simples. Tendremos una fila de Bootstrap que contiene el cuadro de texto de entrada junto con dos botones, uno para agregar el contenido del cuadro de entrada actual a la lista de identificadores y otro para extraer de la API. Tendremos otra fila de Bootstrap que muestra la lista de identificadores que el usuario desea extraer utilizando el grupo de listas de Bootstrap. En código, se ve así:
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> );
Además del componente de la interfaz de usuario, querremos implementar los tres controladores de eventos de la interfaz de usuario que controlan los cambios de datos. El controlador de eventos getPull
, que llama a la API, se implementará en la siguiente sección.
// 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); }
Ahora, estamos listos para implementar la llamada a la API.
Llamada API
Para la llamada a la API, queremos tomar los identificadores que queremos extraer, enviarlos a la API de Python en una solicitud POST y colocar el resultado JSON resultante en la variable curatedTweets
. Luego, si todo va bien, queremos navegar programáticamente a la ruta /display. De lo contrario, registraremos el error en la consola para que podamos depurar más fácilmente.
En modo código, se ve así:
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); }) }
Y con eso, deberíamos estar listos para irnos. No dude en ejecutar la aplicación, agregar un par de identificadores y enviar una solicitud a la API.
Ahora, estamos listos para codificar la página de opinión.
Modo ordenado de opiniones
Debido a que la API de Python ya ordena los tweets por opinión, una vez que tenemos el resultado de la API de Python, la página de opinión no es demasiado difícil.
Plan general
Querremos una interfaz de lista para mostrar los tweets. También necesitaremos un par de componentes de navegación para cambiar al modo de agrupación de temas y volver a la página de entrada.
Para comenzar, definamos el subcomponente del modo SentimentDisplay en el archivo display.js.
Componente SentimentDisplay
SentimentDisplay tomará el objeto curatedTweets
y mostrará los tweets ordenados por sentimiento en una lista. Con la ayuda de React-Bootstrap, el componente es bastante 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> }
Mientras estamos en eso, agreguemos también algo de estilo. Ponga lo siguiente en display.css e impórtelo:
.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; }
Ahora podemos mostrar el componente SentimentDisplay. Cambie la función de Display
así:
const Display = ({curatedTweets}) => { return <SentimentDisplay curatedTweets={curatedTweets} /> };
Aprovechemos también esta oportunidad para codificar los componentes de navegación. Querremos dos botones: el botón "Volver a editar" y el modo de grupo de temas.
Podemos implementar estos botones en una fila separada de Bootstrap justo encima del componente SentimentDisplay, así:
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>
Ejecute la aplicación y extraiga los tweets de un par de identificadores. ¡Se ve bastante ingenioso!
Modo de agrupación de temas
Ahora, queremos implementar el modo de agrupación de temas. Es un poco más complejo que SentimentDisplay, pero nuevamente, algunos componentes de Bootstrap muy útiles nos ayudan inmensamente.
Plan general
Obtendremos todas las frases nominales y las mostraremos como una lista de acordeón. Luego, mostraremos los tweets que contienen las frases nominales una vez que se expanda la lista de acordeón.
Implementación del cambio al modo de agrupación de temas
Primero, implementemos la lógica para cambiar del modo de opinión al modo de agrupación de temas. Empecemos por crear primero el componente stub:
const TopicDisplay = () => { return <div>Topic Display</div> }
Y establezca algo de lógica para crear un modo para mostrarlo. En el componente de visualización principal, agregue las siguientes líneas para crear la lógica para la que se muestra el componente.
// 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'
Y cambie el JSX a lo siguiente para agregar la lógica:
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>
Ahora, debería ver el resguardo de visualización del grupo de temas cuando alterna.
El componente TopicDisplay
Ahora, estamos listos para codificar el componente TopicDisplay
. Como se discutió anteriormente, aprovechará la Lista de acordeón de Bootstrap. La implementación es bastante 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> }
Ejecute la aplicación y debería ver la pantalla de temas.
Ahora la aplicación está lista y estamos listos para construir la aplicación para el emulador.
Ejecutar la aplicación en el emulador
Cordova hace que sea muy fácil ejecutar la aplicación en el emulador. Simplemente ejecuta:
cordova platform add ios # if you haven't done so already cordova run ios
Y deberías ver la aplicación en el emulador. Debido a que Bootstrap es una aplicación web receptiva, la aplicación web se adapta al ancho de un iPhone y todo se ve bastante bien.
Con la aplicación Cordova lista, veamos ahora la implementación de Ionic.
Implementación de reacción iónica
Imprimación iónica
Ionic es una biblioteca de componentes web y un kit de herramientas CLI que facilita la creación de aplicaciones híbridas. Originalmente, Ionic se construyó sobre AngularJS y Cordova, pero desde entonces lanzaron sus componentes en React.js y comenzaron a admitir Capacitor, una plataforma similar a Cordova. Lo que distingue a Ionic es que, aunque esté utilizando componentes web, los componentes se sienten muy similares a una interfaz móvil nativa. Además, la apariencia de los componentes de Ionic se adapta automáticamente al sistema operativo en el que se ejecuta, lo que nuevamente ayuda a que la aplicación se vea y se sienta más nativa y natural. Finalmente, aunque esto está fuera del alcance de nuestro artículo, Ionic también proporciona varias herramientas de compilación que facilitan un poco la implementación de su aplicación.
Para nuestra aplicación, usaremos los componentes React de Ionic para construir la interfaz de usuario mientras aprovechamos parte de la lógica de JavaScript que creamos durante la sección de Cordova.
Configuración de la aplicación
Primero, querremos instalar las herramientas Ionic. Así que vamos a ejecutar lo siguiente:
npm install -g @Ionic/cli native-run cordova-res
Y una vez finalizada la instalación, vayamos a la carpeta del proyecto. Ahora, usamos la CLI de Ionic para crear nuestra nueva carpeta de proyecto:
ionic start twitter-curation-Ionic blank --type=react --capacitor
Mira cómo sucede la magia y ahora ve a la carpeta con:
cd twitter-curation-Ionic
Y ejecuta la aplicación en blanco con:
ionic serve
Nuestra aplicación está así configurada y lista para funcionar. Definamos algunas rutas.
Antes de continuar, notará que Ionic comenzó el proyecto usando TypeScript. Si bien no me esfuerzo por usar TypeScript, tiene algunas características muy buenas y lo usaremos para esta implementación.
Configuración del enrutador
Para esta implementación, usaremos tres rutas: input, sentimentDisplay
y topicDisplay
. Hacemos esto porque queremos aprovechar las funciones de transición y navegación proporcionadas por Ionic y porque estamos usando componentes de Ionic, y las listas de acordeón no vienen empaquetadas con Ionic. Podemos implementar los nuestros, por supuesto, pero para este tutorial, nos quedaremos con los componentes Ionic provistos.
Si navega a App.tsx, debería ver las rutas básicas ya definidas.
Página de entrada
Plan general
Usaremos gran parte de la lógica y el código similares a los de la implementación de Bootstrap, con algunas diferencias clave. Primero, usaremos TypeScript, lo que significa que tendremos anotaciones de tipo para nuestro código, que verá en la siguiente sección. En segundo lugar, usaremos componentes Ionic, que son muy similares en estilo a Bootstrap pero serán sensibles al sistema operativo en su estilo. Por último, navegaremos dinámicamente usando la API de historial como en la versión Bootstrap pero accediendo al historial de forma ligeramente diferente debido a la implementación del Ionic Router.
Configuración
Comencemos configurando el componente de entrada con un componente de código auxiliar. Cree una carpeta debajo de las páginas con el nombre input y cree debajo un archivo llamado Input.tsx. Dentro de ese archivo, coloque el siguiente código para crear un componente React. Tenga en cuenta que debido a que estamos usando TypeScript, es un poco diferente.
import React, {useState} from 'react'; const Input : React.FC = () => { return <div>Input</div>; } export default Input;
Y cambie el componente en App.tsx a:
const App: React.FC = () => ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> );
Ahora, cuando actualice la aplicación, debería ver el componente de código auxiliar de entrada.
Contenedores de datos
Vamos a crear los contenedores de datos ahora. Queremos los contenedores para los identificadores de Twitter ingresados, así como el contenido actual del cuadro de entrada. Debido a que estamos usando TypeScript, necesitaremos agregar las anotaciones de tipo a nuestra invocación de useState
en la función del componente:
const Input : React.FC = () => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }
También querremos un contenedor de datos para contener los valores de retorno de la API. Debido a que el contenido de eso debe compartirse con las otras rutas, las definimos en el nivel App.tsx. Importe useState
de React en el archivo App.tsx y cambie la función del contenedor de la aplicación a la siguiente:
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> ); }
En este punto, si está utilizando un editor con resaltado de sintaxis como Visual Studio Code, debería ver que CuratedTweets se ilumina. Esto se debe a que el archivo no sabe cómo se ve la interfaz de CuratedTweets. Definamos eso ahora. Cree una carpeta en src llamada interfaces y cree un archivo dentro llamado CuratedTweets.tsx. En el archivo, defina la interfaz CuratedTweets de la siguiente manera:
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> }
Ahora la aplicación conoce la estructura de los datos de retorno de la API. Importe la interfaz CuratedTweets en App.tsx. Debería ver la compilación App.tsx sin problemas ahora.
Tenemos que hacer un par de cosas más aquí. Necesitamos pasar la función setCuratedTweets
al componente de entrada y hacer que el componente de entrada sea consciente de esta función.
En App.tsx, modifique la ruta de entrada así:
<Route path="/input" render={() => <Input setCuratedTweets={setCuratedTweets}/>} exact={true} />
Ahora, debería ver que el editor señala otra cosa: Input no sabe que se le pasa el nuevo accesorio, por lo que querremos definirlo en 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); }) }
Agregar un encabezado
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>
¡Eso se ve bien!
Página ordenada de opiniones
Plan general
La página ordenada por sentimiento será muy similar a la página ordenada por sentimiento de la página de Bootstrap pero usando TypeScript e Ionic. También implementaremos la visualización de temas como una ruta separada para aprovechar las funciones de navegación de Ionic mientras se ejecuta en dispositivos móviles, por lo que también necesitaremos dar a esta página la capacidad de navegar a la ruta de visualización de temas.
Configuración de ruta
Comencemos creando una nueva carpeta llamada sentimentsorted y un archivo llamado SentimentSorted.tsx debajo. Exporte un componente de código auxiliar así:
import React from 'react'; const SentimentSorted: React.FC = () => { return <div>Sentiment Sorted</div> } export default SentimentSorted;
Y en App.tsx, importe el componente:
import SentimentSorted from './pages/sentimentsorted/SentimentSorted';
Y agrega la ruta:
<Route path="/display" render={() => <SentimentSorted curatedTweets={curatedTweets} />} />
Recibirá un error de TypeScript que indica que SentimentSorted
no espera los accesorios seleccionados de Tweets, así que abordemos eso ahora en la siguiente sección.
Componentes de la interfaz de usuario
Comencemos definiendo los accesorios del contenedor. Al igual que el componente de entrada:
import CuratedTweets from '../../interfaces/CuratedTweets'; interface ContainerProps { curatedTweets: CuratedTweets }
Y ahora, cambie la visualización del resguardo:
const SentimentSorted: React.FC<ContainerProps> = ({curatedTweets}) => { return <div>Sentiment Sorted</div> }
Y todo debería compilar.
La pantalla es muy simple, es solo una IonList con componentes de pantalla:
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 guarda y extrae algunos tweets usando el componente de entrada, debería ver los tweets que se muestran en una lista.
Ahora, agreguemos los botones de navegación. Agregar a IonGrid:
<IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow>
El switchToInput
es muy fácil de implementar con la API de historial:
const switchToInput = () => { history.goBack(); }
Y ToggleDisplayType
debería ser familiar:
const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'
Ahora tenemos implementado el componente SentimentDisplay
. Ahora, antes de implementar la página de visualización de temas, debemos implementar el componente que muestra todos los temas. Lo haremos en la siguiente sección.
Componente de grupos de temas
Agreguemos una opción de visualización de lista de temas y mostrémosla condicionalmente. Para hacer eso, necesitamos desglosar la lista de visualización de sentimientos. Cambie el nombre de SentimentDisplay a Display y dividamos la lista de visualización de opiniones:
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> }
Observe cómo estamos usando una de las definiciones de clase en la interfaz CuratedTweets. Esto se debe a que estos componentes no necesitan el objeto CuratedTweets completo, sino solo un subconjunto. La lista de temas es muy similar:
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> }
Y ahora, la visualización condicional es fácil de configurar en el componente de visualización:
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> );
Asegúrese de cambiar la exportación predeterminada y ahora estamos listos para implementar la página de visualización de temas.
Página de visualización de tema
Plan general
La página de visualización de temas es una visualización de lista similar a la visualización de sentimientos, pero buscaremos el tema en cuestión desde el parámetro de ruta.
Configuración de ruta
Si has llegado hasta aquí, ya deberías saber qué hacer. Vamos a crear una carpeta de página llamada topicdisplay y TopicDisplay.tsx, escribir un componente de código auxiliar e importarlo a la página App.tsx. Ahora vamos a configurar las rutas:
<Route path="/topicDisplay/:topic" render={() => <TopicDisplay curatedTweets={curatedTweets} /> } />
Ahora estamos listos para implementar el componente de interfaz de usuario.
Componentes de la interfaz de usuario
Primero, creemos la definición de ContainerProps
:
interface ContainerProps { curatedTweets: CuratedTweets } const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { Return <div>Topic Display</div> }
Ahora, necesitaremos recuperar el tema del nombre de la ruta de la URL. Para hacer eso, usaremos la API de historial. Entonces, useHistory
, instanciamos la API de historial y extraigamos el tema del nombre de la ruta. Mientras estamos en eso, implementemos también la funcionalidad de cambio:
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];
Ahora que tenemos los tweets con ese tema en particular, mostrarlos es bastante 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> );
Guarde y ejecute, y las cosas deberían verse bien.
Ejecutar la aplicación en el emulador
Para ejecutar la aplicación en el emulador, simplemente ejecutamos algunos comandos de Ionic para agregar la plataforma móvil y copiar el código, de forma similar a como configuramos las cosas 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
Y deberías ver aparecer la aplicación.
Implementación nativa de React
Imprimación nativa React
React Native adopta un enfoque muy diferente de los enfoques basados en la web de las secciones anteriores. React Native representa su código React como componentes nativos. Esto viene con varias ventajas. En primer lugar, la integración con el sistema operativo subyacente es mucho más profunda, lo que permite al desarrollador aprovechar las nuevas funciones de los teléfonos inteligentes y las funciones específicas del sistema operativo que pueden no estar disponibles a través de Cordova/Capacitor. En segundo lugar, debido a que no hay un motor de renderizado basado en la web en el medio, una aplicación React Native es generalmente más rápida que una escrita con Cordova. Finalmente, debido a que React Native permite la integración de componentes nativos, los desarrolladores pueden ejercer un control mucho más detallado sobre su aplicación.
Para nuestra aplicación, usaremos la lógica de las secciones anteriores y usaremos una biblioteca de componentes React Native llamada NativeBase para codificar nuestra interfaz de usuario.
Configuración de la aplicación
Primero, querrá instalar todos los componentes necesarios de React Native siguiendo las instrucciones aquí.
Una vez instalado React Native, comencemos el proyecto:
react-native init TwitterCurationRN
Deje que se ejecute el script de instalación y, finalmente, se debería crear la carpeta. Cd en la carpeta y ejecute react-native run-ios, y debería ver el emulador emergente con la aplicación de ejemplo.
También querremos instalar NativeBase ya que esa es nuestra biblioteca de componentes. Para ello ejecutamos:
npm install --save native-base react-native link
También queremos instalar el navegador de pila React Native. Corramos:
npm install --save @react-navigation/stack @react-navigation/native
Y
react-native link cd ios pod-install cd
Para completar la vinculación e instalación de los complementos nativos.
Configuración del enrutador
Para el enrutamiento, usaremos el navegador de pila que instalamos en el paso anterior.
Importe los componentes del enrutador:
import { NavigationContainer } from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack';
Y ahora, creamos un navegador de pila:
const Stack = createStackNavigator();
Cambie el contenido del componente de la aplicación para usar el navegador de pila:
const App = () => { return ( <> <StatusBar bar /> <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> </Stack.Navigator> </NavigationContainer> </> ); };
En este punto, obtendrá un error porque la entrada aún no está definida. Definamos un elemento stub solo para hacerlo feliz.
Cree una carpeta de componentes en su proyecto, cree un archivo llamado Entry.jsx y agregue un componente auxiliar como este:
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. }
Importe el componente Entry en su aplicación y debería compilarse.
Ahora, estamos listos para codificar la página de entrada.
Página de entrada
Plan general
Implementaremos una página que es muy similar a la implementada anteriormente pero usando componentes NativeBase. La mayoría de las API de JavaScript y React que usamos, como ganchos y recuperación, todavía están disponibles.
La única diferencia será la forma en que trabajamos con la API de navegación, que verá más adelante.
Componentes de la interfaz de usuario
Los componentes adicionales de NativeBase que usaremos son Container, Content, Input, List, ListItem y Button. Todos estos tienen análogos en Ionic y Bootstrap React, y los desarrolladores de NativeBase lo han hecho muy intuitivo para las personas familiarizadas con estas bibliotecas. Simplemente importe así:
import { Container, Content, Input, Item, Button, List, ListItem, Text } from 'native-base';
Y el componente es:
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>
Y ahora, implementemos los controladores de estado y eventos:
const [input, changeInput] = useState(''); const [handles, changeHandles] = useState([]); const inputChange = (text) => { changeInput(text); } const onAddClicked = () => { const newHandles = [...handles, {key: input}]; changeHandles(newHandles); changeInput(''); }
Y finalmente, la llamada a la 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); }) }
Tenga en cuenta que esta implementación es la misma que la implementación de NativeBase, excepto que estamos navegando de una manera diferente. El navegador de pila pasa a sus componentes un accesorio llamado "navegación" y que se puede usar para navegar entre rutas con .navigate
. Además de simplemente navegar, también puede pasar datos al componente de destino. Usaremos este mecanismo para pasar los datos.
Para hacer que la aplicación se compile, todavía necesitamos que Entry
sea consciente del componente de navegación. Para hacer eso, necesitaremos cambiar la declaración de la función del componente:
export default Entry = ({navigation}) => {
Ahora guarda, y deberías ver la página.
Página ordenada de opiniones
Plan general
Implementaremos la página de opinión de manera muy similar a las secciones anteriores, pero diseñaremos la página de manera un poco diferente y también usaremos la biblioteca de navegación de manera diferente para obtener el valor de retorno de la llamada API.
Debido a que React Native no tiene CSS, necesitaremos definir un objeto StyleSheet o simplemente codificar los estilos en línea. Debido a que compartiremos parte del estilo entre los componentes, creemos una hoja de estilo global. Lo haremos después de configurar la ruta.
Además, debido a que StackNavigator
tiene un botón de navegación Atrás incorporado, no necesitaremos implementar nuestro propio botón Atrás.
Configuración de ruta
La definición de rutas en StackNavigator
es muy simple. Simplemente creamos uno nuevo llamado Stack Screen y le damos el componente, al igual que el enrutador React.
<NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> </Stack.Navigator> </NavigationContainer>
Para que esto funcione, por supuesto, necesitaremos crear un componente de código auxiliar en components/SentimentDisplay.js:
import React from 'react'; import {Text} from 'native-base'; const SentimentDisplay = () => { return <Text>Sentiment Display</Text>; } export default SentimentDisplay;
E importarlo:
import SentimentDisplay from './components/SentimentDisplay';
Ahora, estamos listos para crear la hoja de estilo global.
Hoja de estilo global
Primero, cree un archivo llamado globalStyles.js. Luego, importe el componente StyleSheet desde React Native y defina los estilos:
import {StyleSheet} from 'react-native'; export default StyleSheet.create({ tweet: {paddingTop: 5}, accountName: {fontWeight: '600'}, })
Y estamos listos para codificar la interfaz de usuario.
Componentes de la interfaz de usuario
El componente de la interfaz de usuario es bastante familiar, con la excepción de cómo trabajamos con las rutas. StackNavigator
usar la navegación y la ruta de accesorios especiales de StackNavigator para obtener el estado actual de la aplicación y navegar a la pantalla del tema si el usuario desea ver esa página.
Cambie la definición del componente para acceder a los accesorios de navegación:
const SentimentDisplay = ({route, navigation}) => {
Y ahora, implementamos la lectura del estado de la aplicación y la funcionalidad de navegación:
const {params: {data}} = route; const viewByTopicClicked = () => { navigation.navigate('TopicDisplay', { data }); }
Importar los estilos globales:
import globalStyles from './globalStyles';
Y los componentes:
import { View } from 'react-native'; import {List, Item, Content, ListItem, Container, Text, Button} from 'native-base';
Y finalmente, los componentes:
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>;
Guarde e intente extraer algunos tweets, y debería ver la pantalla de sentimiento. Ahora en la página de agrupación de temas.
Página de agrupación de temas
Plan general
La visualización de temas vuelve a ser muy similar. Usaremos un generador de controladores para crear una función de navegación para navegar a la página de visualización de un elemento de tema específico, y también definiremos una hoja de estilo que sea específica para esta página.
Una cosa nueva que haremos es implementar TouchableOpacity, que es un componente específico de React Native que funciona como un botón.
Configuración de ruta
La definición de ruta es la misma que antes:
<Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> <Stack.Screen name="TopicDisplay" component={TopicDisplay} /> </Stack.Navigator>
Los componentes 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 importarlo:
import TopicDisplay from './components/TopicDisplay;
Componentes de la interfaz de usuario
Mucho de esto parecerá muy familiar. Importar las funciones de la biblioteca:
import { View, TouchableOpacity, StyleSheet } from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base';
Importar los estilos globales:
import globalStyles from './globalStyles';
Defina los estilos personalizados:
const styles = StyleSheet.create({ topicName: {fontWeight: '600'}, })
Defina los accesorios de navegación:
export default TopicDisplay = ({route, navigation}) => {
Defina los controladores de datos y acciones. Observe que estamos usando un generador de controladores, una función que devuelve una función:
const {params: {data}} = route; const specificItemPressedHandlerBuilder = (topic) => () => { navigation.navigate('TopicDisplayItem', { data, topic }); }
Y ahora, los componentes. Tenga en cuenta que estamos usando TouchableOpacity, que puede tener un controlador onPress
. Podríamos haber usado TouchableTransparency también, pero la animación de hacer clic y mantener presionada de TouchableOpacity se adaptaba mejor a nuestra aplicación.
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>;
Y esto debería hacerlo. ¡Guarda y prueba la aplicación!
Ahora, en la página del elemento de visualización del tema.
Tema Elemento de visualización Página
Plan general
La página Elemento de visualización de tema es muy similar y todas las idiosincrasias se abordan en las otras secciones, por lo que debería ser fácil navegar desde aquí.
Configuración de ruta
Agregaremos la definición de la ruta:
<Stack.Screen name="TopicDisplayItem" component={TopicDisplayItem} />
Añadir la importación:
import TopicDisplayItem from './components/TopicDisplayItem';
Y crea el componente stub. En lugar de solo un componente simple, también importemos los componentes de NativeBase que usaremos y definamos los accesorios de ruta:
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;
Componentes de la interfaz de usuario
El componente de interfaz de usuario es bastante simple. Lo hemos visto antes y en realidad no estamos implementando ninguna lógica personalizada. Entonces, ¡vamos a por ello! Tomar una respiración profunda…
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>;
¡Ahorre, y deberíamos estar listos para comenzar! Ahora estamos listos para ejecutar la aplicación en el emulador... espera, ¿no lo hemos hecho ya?
Ejecutar la aplicación
Bueno, dado que está trabajando con React Native, ya está ejecutando la aplicación en el emulador, por lo que esta parte ya está solucionada. Esta es una de las mejores cosas del entorno de desarrollo de React Native.
¡Uf! Con eso, hemos terminado con la parte de codificación de este artículo. Veamos lo que aprendimos sobre las tecnologías.
Comparando las Tecnologías
Córdoba: pros y contras
Lo mejor de Cordova es la gran velocidad con la que un desarrollador web experto puede codificar algo funcional y razonablemente presentable. Las habilidades de desarrollo web y la experiencia se transfieren fácilmente porque, después de todo, está codificando una aplicación web. El proceso de desarrollo es rápido y simple, y acceder a la API de Cordova también es simple e intuitivo.
Los inconvenientes de usar Cordova directamente provienen principalmente de la dependencia excesiva de los componentes web. Los usuarios esperan una experiencia de usuario y un diseño de interfaz específicos cuando usan aplicaciones móviles, y cuando una aplicación se siente como un sitio móvil, la experiencia puede ser un poco discordante. Además, la mayoría de las funciones integradas en las aplicaciones, como las animaciones de transición y las utilidades de navegación, deben implementarse manualmente.
Iónico: pros y contras
La mejor parte de Ionic fue la cantidad de funciones centradas en dispositivos móviles que obtuve de forma "gratuita". Al codificar como si hubiera codificado una aplicación web, pude crear una aplicación que se ve mucho más amigable para dispositivos móviles que simplemente usar Cordova y React-Bootstrap. Hubo animación de navegación, botones con estilos de aspecto nativo y muchas opciones de interfaz de usuario que hicieron que la experiencia del usuario fuera muy fluida.
El inconveniente de usar Ionic fue causado en parte por sus puntos fuertes. Primero, a veces era difícil imaginar cómo se comportaría la aplicación en varios entornos. El hecho de que la aplicación se viera de una manera no significaba que la misma ubicación de la interfaz de usuario se vería igual en otro entorno. En segundo lugar, Ionic se asienta sobre muchas piezas de tecnologías subyacentes, y resultó difícil acceder a algunos de los componentes. Por último, esto es específico de Ionic-React, pero debido a que Ionic se creó primero para Angular, muchas características de Ionic-React parecían tener menos documentación y soporte. Sin embargo, el equipo de Ionic parece estar muy atento a las necesidades de los desarrolladores de React y ofrece nuevas funciones rápidamente.
Reaccionar nativo: pros y contras
React Native tuvo una experiencia de usuario muy fluida al desarrollarse en dispositivos móviles. Al conectarse directamente al emulador, no era ningún misterio cómo se vería la aplicación. La interfaz del depurador basado en la web fue extremadamente útil para aplicar técnicas de depuración del mundo de las aplicaciones web, y el ecosistema es bastante sólido.
El inconveniente de React Native proviene de su proximidad a la interfaz nativa. Muchas bibliotecas basadas en DOM no se podían usar, lo que significaba tener que aprender nuevas bibliotecas y mejores prácticas. Sin el beneficio de CSS, diseñar la aplicación era algo menos intuitivo. Finalmente, con muchos componentes nuevos para aprender (p. ej., Vista en lugar de div, componente de texto que envuelve todo, botones frente a TouchableOpacity frente a TouchableTransparency, etc.), hay una pequeña curva de aprendizaje al principio si alguien está entrando en el Reacciona Mundo nativo con poco conocimiento previo de la mecánica.
Cuándo usar cada tecnología
Debido a que Cordova, Ionic y React Native tienen ventajas y desventajas muy sólidas, cada tecnología tiene un contexto en el que disfrutaría de la mejor productividad y rendimiento.
Si ya tiene una aplicación existente que prioriza la web con una fuerte identidad de marca que rodea el diseño de la interfaz de usuario y la apariencia general, su mejor opción sería Cordova, que le brinda acceso a las funciones nativas del teléfono inteligente y le permite reutilizar la mayor parte de su componentes web y preservar su identidad de marca en el proceso. Para aplicaciones relativamente simples que utilizan un marco receptivo, es posible que pueda crear una aplicación móvil con muy pocos cambios necesarios. Sin embargo, su aplicación se parecerá menos a una aplicación y más a una página web, y algunos de los componentes que la gente espera de una aplicación móvil se codificarán por separado. Por lo tanto, recomiendo Cordova en los casos en los que se encuentre en un proyecto web primero que transfiera la aplicación al dispositivo móvil.
Si está comenzando a codificar una nueva aplicación con una filosofía de aplicación primero, pero el conjunto de habilidades de su equipo se basa principalmente en el desarrollo web, le recomiendo Ionic. La biblioteca de Ionic le permite escribir rápidamente código que se ve y se siente cerca de los componentes nativos, al mismo tiempo que le permite aplicar sus habilidades e instintos como desarrollador web. Descubrí que las mejores prácticas de desarrollo web se aplicaban fácilmente al desarrollo con Ionic, y el estilo con CSS funcionaba a la perfección. Además, la versión móvil del sitio parece mucho más nativa que un sitio web codificado con un marco CSS receptivo. Sin embargo, en algunos pasos del camino, descubrí que la integración de la API React-Ionic-Native requiere algunos ajustes manuales, lo que podría llevar mucho tiempo. Por lo tanto, recomiendo Ionic en los casos en que su aplicación se desarrolle desde cero y desee compartir una cantidad significativa de código entre una aplicación web compatible con dispositivos móviles y una aplicación móvil.
Si está codificando una nueva aplicación con algún código base nativo implementado, puede probar React Native. Incluso si no usa código nativo, también podría ser la mejor opción en los casos en los que ya está familiarizado con React Native, o cuando su principal preocupación es la aplicación móvil en lugar de una aplicación híbrida. Habiendo centrado la mayor parte de mis esfuerzos de desarrollo front-end en el desarrollo web, inicialmente descubrí que comenzar con React Native tenía más curva de aprendizaje que Ionic o Cordova debido a las diferencias en la organización de los componentes y las convenciones de codificación. Sin embargo, una vez que se aprenden estos matices, la experiencia de codificación es bastante fluida, especialmente con la ayuda de una biblioteca de componentes como NativeBase. Dada la calidad del entorno de desarrollo y el control sobre la aplicación, si el front-end de su proyecto es principalmente una aplicación móvil, recomendaría React Native como su herramienta preferida.
Temas futuros
Uno de los temas que no tuve tiempo de explorar fue la facilidad de acceder a APIs nativas como cámara, geolocalización o autenticación biométrica. Uno de los grandes beneficios del desarrollo móvil es la accesibilidad de un rico ecosistema de API al que generalmente no se puede acceder desde el navegador.
En artículos futuros, me gustaría explorar la facilidad de desarrollar estas aplicaciones nativas habilitadas para API utilizando varias tecnologías multiplataforma.
Conclusión
Hoy, implementamos una aplicación de conservación de Twitter utilizando tres tecnologías de desarrollo móvil multiplataforma diferentes. Espero que esto le haya dado una buena idea de cómo es cada tecnología y lo haya inspirado a desarrollar su propia aplicación basada en React.
¡Gracias por leer!