PNL full-stack com React: Ionic vs Cordova vs React Native
Publicados: 2022-03-11Nos aproximadamente 15 anos desde que a Apple lançou o primeiro iPhone, o cenário de desenvolvimento de software mudou drasticamente. À medida que os smartphones ganham ampla adoção e continuam a crescer em recursos exclusivos, os usuários preferem cada vez mais acessar serviços de software por meio de dispositivos móveis em vez de desktops ou laptops. Os smartphones oferecem recursos como geolocalização, autenticação biométrica e detecção de movimento, muitos dos quais as plataformas de desktop estão apenas começando a copiar. Em alguns dados demográficos, um smartphone ou um dispositivo móvel semelhante é o principal meio de consumo de software, ignorando completamente os computadores.
As empresas perceberam essa mudança e a reforçaram de maneiras importantes. Os aplicativos móveis não são mais uma reflexão tardia. Aplicativos que vão desde Robinhood, uma empresa de corretagem financeira, até Instagram, uma empresa de mídia social, e Uber, uma empresa de transporte, estão adotando uma estratégia de desenvolvimento mobile-first. Se houver um aplicativo de desktop, ele geralmente é oferecido como um complemento ao aplicativo móvel, em vez do foco principal.
Para desenvolvedores full-stack, adaptar-se a essas tendências em mudança é crucial. Felizmente, existem muitas tecnologias maduras e bem suportadas disponíveis para ajudar os desenvolvedores da Web a aplicar suas habilidades ao desenvolvimento móvel. Hoje, vamos explorar três dessas tecnologias: Cordova, Ionic e React Native. Usaremos o React.js, um dos frameworks mais populares para desenvolvimento web front-end, como nossa principal tecnologia de desenvolvimento. Embora nos concentremos no desenvolvimento de um aplicativo para iPhone, essas tecnologias são multiplataforma e serão compiláveis para a plataforma Android.
O que vamos construir hoje
Construiremos um aplicativo que usa Processamento de Linguagem Natural (NLP) para processar e organizar feeds do Twitter. O aplicativo permitirá que o usuário selecione um conjunto de identificadores do Twitter, obtenha as atualizações mais recentes usando uma API do Twitter e categorize os tweets com base no sentimento e no tópico. O usuário poderá visualizar os tweets com base no sentimento ou no tópico.
Processo interno
Antes de construir o front-end, vamos querer construir o back-end. Manteremos o back-end simples por enquanto - usaremos análise de sentimento básica e pronta para uso e marcação de parte do discurso, juntamente com um pouco de limpeza de dados para lidar com problemas específicos do conjunto de dados. Usaremos uma biblioteca NLP de código aberto chamada TextBlob e serviremos o resultado no Flask.
Análise de sentimentos, marcação de parte da fala e PNL: uma cartilha rápida
Se você nunca trabalhou com aplicativos de análise de linguagem natural antes, esses termos podem ser muito estranhos para você. PNL é um termo abrangente para tecnologias que analisam e processam dados de linguagem humana natural. Embora este seja um tópico amplo, existem muitos desafios comuns a todas as tecnologias que abordam essa área. Por exemplo, a linguagem humana, ao contrário da linguagem de programação ou dos dados numéricos, tende a ser estruturada de forma flexível devido à natureza permissiva da gramática da linguagem humana. Além disso, a linguagem humana tende a ser extremamente contextual, e uma frase pronunciada ou escrita em um contexto pode não se traduzir em outro contexto. Finalmente, estrutura e contexto à parte, a linguagem é extremamente complexa. Palavras mais abaixo em um parágrafo podem alterar o significado de uma frase no início do parágrafo. O vocabulário pode ser inventado, redefinido ou alterado. Todas essas complexidades dificultam a aplicação cruzada das técnicas de análise de dados.
A Análise de Sentimentos é um subcampo da PNL que se concentra na compreensão da emotividade de uma passagem de linguagem natural. Embora a emoção humana seja inerentemente subjetiva e, portanto, difícil de definir tecnologicamente, a análise de sentimentos é um subcampo que tem imensa promessa comercial. Algumas aplicações da análise de sentimentos incluem a classificação de avaliações de produtos para identificar avaliações positivas e negativas de vários recursos, detectar o humor de um e-mail ou um discurso e agrupar as letras das músicas por humor. Se você estiver procurando por uma explicação mais detalhada da Análise de Sentimentos, você pode ler meu artigo sobre como construir um aplicativo baseado em Análise de Sentimentos aqui.
A marcação de parte da fala, ou marcação POS, é um subcampo muito diferente. O objetivo da marcação POS é identificar a parte do discurso de uma determinada palavra em uma frase usando informações gramaticais e contextuais. Identificar essa relação é uma tarefa muito mais difícil do que inicialmente aparenta - uma palavra pode ter partes do discurso muito diferentes com base no contexto e na estrutura da frase, e as regras nem sempre são claras, mesmo para humanos. Felizmente, muitos modelos disponíveis hoje fornecem modelos poderosos e versáteis integrados à maioria das principais linguagens de programação. Se você quiser saber mais, leia meu artigo sobre marcação de PDV aqui.
Flask, TextBlob e Tweepy
Para nosso back-end de PNL, usaremos Flask, TextBlob e Tweepy. Usaremos o Flask para construir um servidor pequeno e leve, o TextBlob para executar nosso processamento de linguagem natural e o Tweepy para obter os tweets da API do Twitter. Antes de começar a codificar, você também deve obter uma chave de desenvolvedor do Twitter para poder recuperar os tweets.
Podemos escrever um back-end muito mais sofisticado e usar tecnologias de PNL mais complexas, mas para nossos propósitos de hoje, manteremos o back-end o mais simples possível.
Código de back-end
Agora, estamos prontos para começar a codificar. Ative seu editor e terminal Python favoritos e vamos começar!
Primeiro, vamos querer instalar os pacotes necessários.
pip install flask flask-cors textblob tweepy python -m textblob.download_corpora
Agora, vamos escrever o código para nossa funcionalidade.
Abra um novo script Python, chame-o de server.py e importe as bibliotecas necessárias:
import tweepy from textblob import TextBlob from collections import defaultdict
Vamos agora escrever algumas funções 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
Agora que temos as funções auxiliares escritas, podemos juntar tudo com algumas funções 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
Agora você pode executar a função download_analyze_tweets
em uma lista de handles que deseja seguir e deverá ver os resultados.
Executei o seguinte 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)
A execução disso produziu os seguintes resultados. Os resultados são obviamente dependentes do tempo, então se você ver algo semelhante, você está no caminho certo.
[{'account': '@spacex', 'tweet': 'Falcon 9… [{'account': '@nasa', 'tweet': 'Our Mars rove… {'Falcon': [{'account': '@spacex', 'tweet': 'Falc….
Agora podemos construir o servidor Flask, que é bem simples. Crie um arquivo vazio chamado server.py e escreva o seguinte 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)
Execute o servidor e agora você poderá enviar uma solicitação de postagem para o servidor usando um cliente HTTP de sua escolha. Passe {accounts: [“@NASA”, “@SpaceX”]} como um argumento json e você verá a API retornar algo semelhante ao que foi retornado no código de análise do Twitter.
Agora que temos um servidor, estamos prontos para codificar o front-end. Por causa de uma pequena nuance na rede em um emulador de telefone, recomendo que você implante sua API em algum lugar. Caso contrário, você desejará detectar se seu aplicativo está sendo executado em um emulador e enviar solicitações para <Your Computer IP>:5000
em vez de localhost:5000
quando estiver em um emulador. Se você implantar o código, poderá simplesmente emitir a solicitação para esse URL.
Há muitas opções para implantar o servidor. Para um servidor de depuração simples e gratuito com configuração mínima necessária, recomendo algo como PythonAnywhere, que deve ser capaz de executar esse servidor imediatamente.
Agora que codificamos o servidor back-end, vamos ver o front-end. Começaremos com uma das opções mais convenientes para um desenvolvedor web: Cordova.
Implementação do Apache Cordova
Cartilha Cordova
Apache Cordova é uma tecnologia de software para ajudar os desenvolvedores da Web a atingir plataformas móveis. Aproveitando os recursos do navegador da Web implementados em plataformas de smartphones, o Cordova envolve o código do aplicativo da Web em um contêiner de aplicativo nativo para criar um aplicativo. Cordova não é simplesmente um navegador sofisticado, no entanto. Por meio da API do Cordova, os desenvolvedores da Web podem acessar muitos recursos específicos de smartphones, como suporte offline, serviços de localização e câmera no dispositivo.
Para nosso aplicativo, escreveremos um aplicativo usando React.js como framework JS e React-Bootstrap como framework CSS. Como o Bootstrap é um framework CSS responsivo, ele já possui suporte para execução em telas menores. Depois que o aplicativo estiver escrito, vamos compilá-lo em um aplicativo da Web usando o Cordova.
Configurando o aplicativo
Começaremos fazendo algo único para configurar o aplicativo Cordova React. Em um artigo do Medium , o desenvolvedor Shubham Patil explica o que estamos fazendo. Essencialmente, estamos configurando um ambiente de desenvolvimento React usando o React CLI e, em seguida, um ambiente de desenvolvimento Cordova usando o Cordova CLI, antes de finalmente mesclar os dois.
Para começar, execute os dois comandos a seguir em sua pasta de código:
cordova create TwitterCurationCordova create-react-app twittercurationreact
Depois que a configuração estiver concluída, vamos querer mover o conteúdo da pasta public e src do aplicativo React para o aplicativo Cordova. Em seguida, no package.json, copie os scripts, a lista de navegadores e as dependências do projeto React. Adicione também "homepage": "./"
na raiz do package.json para habilitar a compatibilidade com o Cordova.
Depois que o package.json for mesclado, vamos querer alterar o arquivo public/index.html para funcionar com o Cordova. Abra o arquivo e copie as meta tags de www/index.html, bem como o script no final da tag body quando o Cordova.js for carregado.
Em seguida, altere o arquivo src/index.js para detectar se ele está sendo executado no Cordova. Se estiver rodando no Cordova, vamos querer rodar o código de renderização dentro do manipulador de eventos deviceready. Se estiver rodando em um navegador normal, apenas renderize imediatamente.
const renderReactDom = () => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.cordova) { document.addEventListener('deviceready', () => { renderReactDom(); }, false); } else { renderReactDom(); }
Por fim, precisamos configurar nosso pipeline de implantação. Adicione a definição abaixo no arquivo config.xml:
<hook type="before_prepare" src="hooks/prebuild.js" />
E coloque o seguinte script em 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); }); }); }); };
Isso executa a compilação do React e coloca a pasta de compilação no local apropriado antes que a compilação do Cordova seja iniciada, automatizando assim o processo de implantação.
Agora, podemos tentar executar nosso aplicativo. Execute o seguinte na linha de comando:
npm install rimraf npm install npm run start
Você deve ver o aplicativo React configurado e em execução no navegador. Adicione Cordova agora:
cordova platform add iOS cordova run iOS
E você deve ver o aplicativo React rodando no emulador.
Configuração de roteador e pacotes
Para configurar parte da infraestrutura necessária para construir o aplicativo, vamos começar instalando os pacotes necessários:
npm install react-bootstrap react-router react-router-dom
Agora vamos configurar o roteamento e, ao fazê-lo, também configuraremos um objeto de estado global simples que será compartilhado por todos os componentes. Em um aplicativo de produção, queremos usar um sistema de gerenciamento de estado como Redux ou MobX, mas vamos mantê-lo simples por enquanto. Acesse App.js e configure a rota assim:
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> }
Com esta definição de rota, introduzimos duas rotas que precisaremos implementar: Input e Display. Observe que a variável curatedTweets
está sendo passada para Display e a variável setCuratedTweets
está sendo passada para Input. Isso significa que o componente de entrada poderá chamar a função para definir a variável curatedTweets
e Display obterá a variável a ser exibida.
Para começar a codificar os componentes, vamos criar uma pasta em /src chamada /src/components. Em /src/components, crie outra pasta chamada /src/components/input e dois arquivos abaixo: input.js e input.css. Faça o mesmo para o componente Display - crie /src/components/display e abaixo: display.js e display.css.
Sob eles, vamos criar componentes de stub, assim:
import React from 'react'; import 'input.css' const Input = () => <div>Input</div>; export default Input
E o mesmo para Display:
import React from 'react'; import display.css' const Display = () => <div>Display</div>; export default Display
Com isso, nosso wireframing está completo e o aplicativo deve ser executado. Vamos agora codificar a página de entrada.
Página de entrada
Plano geral
Antes de escrevermos o código, vamos pensar no que queremos que nossa página de entrada faça. Obviamente, queremos uma maneira de os usuários inserirem e editarem os identificadores do Twitter dos quais desejam extrair. Também queremos que os usuários possam indicar que terminaram. Quando os usuários indicarem que terminaram, desejaremos extrair os tweets selecionados de nossa API de curadoria Python e, finalmente, navegar até o componente Display.
Agora que sabemos o que queremos que nosso componente faça, estamos prontos para codificar.
Configurando o arquivo
Vamos começar importando o withRouter
da biblioteca do React Router para obter acesso à funcionalidade de navegação, os componentes do React Bootstrap que precisamos, assim:
import React, {useState} from 'react'; import {withRouter} from 'react-router-dom'; import {ListGroup, Button, Form, Container, Row, Col} from 'react-bootstrap'; import './input.css';
Agora, vamos definir a função stub para Input. Sabemos que Input obtém a função setCuratedTweets
e também queremos dar a ele a capacidade de navegar para a rota de exibição depois de definir os tweets curados de nossa API Python. Portanto, vamos querer tirar dos adereços setCuratedTweets
e history (para navegação).
const Input = ({setCuratedTweets, history}) => { return <div>Input</div> }
Para dar acesso à API de histórico, vamos envolvê-lo com withRouter
na instrução de exportação no final do arquivo:
export default withRouter(Input);
Contêineres de dados
Vamos configurar os contêineres de dados usando React Hooks. Já importamos o hook useState
para podermos adicionar o seguinte código ao corpo do componente Input:
const [handles, setHandles] = useState([]); const [handleText, setHandleText] = useState('');
Isso cria o contêiner e os modificadores para handles, que conterão a lista de handles que o usuário deseja extrair, e o handleText
, que conterá o conteúdo da caixa de texto que o usuário usa para inserir o handle.
Agora, vamos codificar os componentes da interface do usuário.
Componentes de IU
Os componentes da interface do usuário serão bastante simples. Teremos uma linha Bootstrap que contém a caixa de texto de entrada junto com dois botões, um para adicionar o conteúdo da caixa de entrada atual à lista de manipuladores e outro para extrair da API. Teremos outra linha do Bootstrap que exibe a lista de handles que o usuário deseja extrair usando o grupo de lista do Bootstrap. No código, fica assim:
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> );
Além do componente de interface do usuário, desejaremos implementar os três manipuladores de eventos de interface do usuário que tratam das alterações de dados. O manipulador de eventos getPull
, que chama a API, será implementado na próxima seção.
// 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); }
Agora, estamos prontos para implementar a chamada da API.
Chamada de API
Para a chamada da API, queremos pegar os handles que queremos puxar, enviá-los para a API Python em uma solicitação POST e colocar o resultado JSON resultante na variável curatedTweets
. Então, se tudo correr bem, queremos navegar programaticamente para a rota /display. Caso contrário, registraremos o erro no console para que possamos depurar mais facilmente.
No modo de código, fica assim:
const pullAPI = (e) => { e.preventDefault(); fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts: handles }) }).then(r=>r.json()).then(j => { setCuratedTweets(j); history.push('/display'); }) .catch(e => { console.log(e); }) }
E com isso, devemos estar prontos para ir. Sinta-se à vontade para executar o aplicativo, adicionar alguns identificadores e enviar uma solicitação para a API.
Agora, estamos prontos para codificar a página de sentimento.
Modo de classificação de sentimento
Como a API Python já classifica os tweets por sentimento, uma vez que temos o resultado da API Python, a página de sentimento não é muito difícil.
Plano geral
Vamos querer uma interface de lista para mostrar os tweets. Também queremos que alguns componentes de navegação mudem para o modo de agrupamento de tópicos e voltem para a página de entrada.
Para começar, vamos definir o subcomponente do modo SentimentDisplay no arquivo display.js.
Componente de exibição de sentimento
O SentimentDisplay pegará o objeto curatedTweets
e exibirá os tweets classificados por sentimento em uma lista. Com a ajuda do React-Bootstrap, o componente é bem simples:
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> }
Enquanto estamos nisso, vamos também adicionar um pouco de estilo. Coloque o seguinte em display.css e importe-o:
.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; }
Agora podemos mostrar o componente SentimentDisplay. Altere a função Display
assim:
const Display = ({curatedTweets}) => { return <SentimentDisplay curatedTweets={curatedTweets} /> };
Vamos também aproveitar esta oportunidade para codificar os componentes de navegação. Vamos querer dois botões - o botão "Voltar para editar" e o Modo Grupo de Tópicos.
Podemos implementar esses botões em uma linha separada do Bootstrap logo acima do componente SentimentDisplay, assim:
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>
Execute o aplicativo e puxe os tweets de algumas alças. Parece bem bacana!
Modo de agrupamento de tópicos
Agora, queremos implementar o modo de agrupamento de tópicos. É um pouco mais complexo que o SentimentDisplay, mas, novamente, alguns componentes muito úteis do Bootstrap nos ajudam imensamente.
Plano geral
Vamos pegar todas as frases nominais e exibi-las como uma lista de acordeão. Em seguida, renderizaremos os tweets contendo as frases nominais assim que a lista de acordeões for expandida.
Implementando a mudança para o modo de agrupamento de tópicos
Primeiro, vamos implementar a lógica para alternar do modo de sentimento para o modo de agrupamento de tópicos. Vamos começar criando o componente stub primeiro:
const TopicDisplay = () => { return <div>Topic Display</div> }
E defina alguma lógica para criar um modo para exibi-lo. No componente de exibição principal, adicione as seguintes linhas para criar a lógica para qual componente é exibido.
// controls the display mode. Remember to import {useState} from 'react' const [displayType, setDisplayType] = useState('Sentiment'); // Switch the Display Mode const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } // determines the text on the mode switch button const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'
E altere o JSX para o seguinte para adicionar a 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>
Agora, você deve ver o stub de exibição do grupo de tópicos ao alternar.
O componente de exibição do tópico
Agora, estamos prontos para codificar o componente TopicDisplay
. Conforme discutido anteriormente, ele aproveitará a Lista de acordeão do Bootstrap. A implementação é realmente bastante simples:
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> }
Execute o aplicativo e você deverá ver a exibição do tópico.
Agora o aplicativo está pronto e estamos prontos para construir o aplicativo para o emulador.
Executando o aplicativo no emulador
O Cordova facilita muito a execução do aplicativo no emulador. Basta executar:
cordova platform add ios # if you haven't done so already cordova run ios
E você deve ver o aplicativo no emulador. Como o Bootstrap é um aplicativo da Web responsivo, o aplicativo da Web se adapta à largura de um iPhone e tudo parece muito bom.
Com o aplicativo Cordova pronto, vamos agora ver a implementação do Ionic.
Implementação de reação iônica
Primer Iônico
O Ionic é uma biblioteca de componentes da Web e um kit de ferramentas CLI que facilita a criação de aplicativos híbridos. Originalmente, o Ionic foi construído em cima do AngularJS e do Cordova, mas desde então eles lançaram seus componentes no React.js e começaram a oferecer suporte ao Capacitor, uma plataforma semelhante ao Cordova. O que diferencia o Ionic é que, embora você esteja usando componentes da Web, os componentes parecem muito semelhantes a uma interface móvel nativa. Além disso, a aparência dos componentes Ionic se adaptam automaticamente ao sistema operacional em que são executados, o que novamente ajuda o aplicativo a parecer mais nativo e natural. Por fim, embora isso esteja fora do escopo de nosso artigo, o Ionic também fornece várias ferramentas de compilação que facilitam um pouco a implantação de seu aplicativo.
Para nosso aplicativo, usaremos os componentes React do Ionic para construir a interface do usuário enquanto aproveitamos parte da lógica JavaScript que construímos durante a seção Cordova.
Configurando o aplicativo
Primeiro, vamos querer instalar as ferramentas Ionic. Então vamos executar o seguinte:
npm install -g @Ionic/cli native-run cordova-res
E após a instalação, vamos para a pasta do projeto. Agora, usamos o Ionic CLI para criar nossa nova pasta de projeto:
ionic start twitter-curation-Ionic blank --type=react --capacitor
Veja a mágica acontecer, e agora entre na pasta com:
cd twitter-curation-Ionic
E execute o aplicativo em branco com:
ionic serve
Nosso aplicativo está assim configurado e pronto para ser usado. Vamos definir algumas rotas.
Antes de prosseguirmos, você notará que o Ionic iniciou o projeto usando o TypeScript. Embora eu não saia do meu caminho para usar o TypeScript, ele tem alguns recursos muito bons, e nós o usaremos para esta implementação.
Configuração do roteador
Para esta implementação, usaremos três rotas - input, sentimentDisplay
e topicDisplay
. Fazemos isso porque queremos aproveitar os recursos de transição e navegação fornecidos pelo Ionic e porque estamos usando componentes Ionic, e as listas de acordeão não vêm pré-empacotadas com o Ionic. Podemos implementar nossos próprios, é claro, mas para este tutorial, ficaremos com os componentes Ionic fornecidos.
Se você navegar até o App.tsx, deverá ver as rotas básicas já definidas.
Página de entrada
Plano geral
Usaremos muita lógica e código semelhantes à implementação do Bootstrap, com algumas diferenças importantes. Primeiro, usaremos o TypeScript, o que significa que teremos anotações de tipo para nosso código, que você verá na próxima seção. Em segundo lugar, usaremos componentes Ionic, que são muito semelhantes em estilo ao Bootstrap, mas serão sensíveis ao sistema operacional em seu estilo. Por fim, navegaremos dinamicamente usando a API de histórico como na versão Bootstrap, mas acessando o histórico de maneira um pouco diferente devido à implementação do Ionic Router.
Configurando
Vamos começar configurando o componente de entrada com um componente stub. Crie uma pasta sob as páginas chamada input e crie sob ela um arquivo chamado Input.tsx. Dentro desse arquivo, coloque o seguinte código para criar um componente React. Observe que, como estamos usando o TypeScript, é um pouco diferente.
import React, {useState} from 'react'; const Input : React.FC = () => { return <div>Input</div>; } export default Input;
E altere o componente em App.tsx para:
const App: React.FC = () => ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> );
Agora, ao atualizar o aplicativo, você deverá ver o componente de stub de entrada.
Contêineres de dados
Vamos criar os contêineres de dados agora. Queremos os contêineres para os identificadores do Twitter inseridos, bem como o conteúdo atual da caixa de entrada. Como estamos usando o TypeScript, precisaremos adicionar as anotações de tipo à nossa invocação useState
na função do componente:
const Input : React.FC = () => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }
Também queremos que um contêiner de dados mantenha os valores de retorno da API. Como o conteúdo dela precisa ser compartilhado com as outras rotas, nós as definimos no nível do App.tsx. Importe useState
do React no arquivo App.tsx e altere a função do contêiner do aplicativo para o seguinte:
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> ); }
Neste ponto, se você estiver usando um editor com realce de sintaxe como o Visual Studio Code, você deverá ver os CuratedTweets acesos. Isso ocorre porque o arquivo não sabe como é a interface CuratedTweets. Vamos definir isso agora. Crie uma pasta em src chamada interfaces e crie um arquivo dentro dela chamado CuratedTweets.tsx. No arquivo, defina a interface CuratedTweets da seguinte forma:
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> }
Agora, o aplicativo conhece a estrutura dos dados de retorno da API. Importe a interface CuratedTweets em App.tsx. Você deve ver a compilação do App.tsx sem problemas agora.
Precisamos fazer mais algumas coisas aqui. Precisamos passar a função setCuratedTweets
para o componente Input e tornar o componente Input ciente dessa função.
No App.tsx, modifique a rota de entrada assim:
<Route path="/input" render={() => <Input setCuratedTweets={setCuratedTweets}/>} exact={true} />
Agora, você deve ver o editor sinalizar outra coisa - Input não sabe sobre o novo prop sendo passado para ele, então vamos querer defini-lo em 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.
Chamada de API
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); }) }
Adicionando um cabeçalho
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>
Agora que parece bom!
Página classificada de sentimento
Plano geral
A página classificada por sentimento será bastante semelhante à página classificada por sentimento da página Bootstrap, mas usando TypeScript e Ionic. Também implementaremos a Exibição de tópicos como uma rota separada para aproveitar os recursos de navegação do Ionic durante a execução em dispositivos móveis, portanto, precisaremos fornecer a esta página a capacidade de navegar para a rota Exibição de tópicos também.
Configuração de rota
Vamos começar criando uma nova pasta chamada sentimentsorted e um arquivo chamado SentimentSorted.tsx abaixo. Exporte um componente stub assim:
import React from 'react'; const SentimentSorted: React.FC = () => { return <div>Sentiment Sorted</div> } export default SentimentSorted;
E no App.tsx, importe o componente:
import SentimentSorted from './pages/sentimentsorted/SentimentSorted';
E adicione a rota:
<Route path="/display" render={() => <SentimentSorted curatedTweets={curatedTweets} />} />
Você receberá um erro do TypeScript dizendo que o SentimentSorted
não está esperando as props curatedTweets, então vamos cuidar disso agora na próxima seção.
Componentes de IU
Vamos começar definindo as props do container. Muito parecido com o componente de entrada:
import CuratedTweets from '../../interfaces/CuratedTweets'; interface ContainerProps { curatedTweets: CuratedTweets }
E agora, altere a exibição do stub:
const SentimentSorted: React.FC<ContainerProps> = ({curatedTweets}) => { return <div>Sentiment Sorted</div> }
E tudo deve compilar.
A exibição é muito simples, é apenas uma IonList com componentes de exibição:
return <IonGrid> <IonRow> <IonCol> <IonList> {(curatedTweets.sentimentSorted).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid>
Se você salvar e extrair alguns tweets usando o componente de entrada, deverá ver os tweets exibidos em uma lista.
Agora, vamos adicionar os botões de navegação. Adicione ao IonGrid:
<IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow>
O switchToInput
é muito fácil de implementar com a API de histórico:
const switchToInput = () => { history.goBack(); }
E ToggleDisplayType
deve ser familiar:
const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'
Agora temos o componente SentimentDisplay
implementado. Agora, antes de implementarmos a Página de Exibição de Tópicos, precisamos implementar o componente que exibe todos os tópicos. Faremos isso na próxima seção.
Componente de grupos de tópicos
Vamos adicionar uma opção de exibição de lista de tópicos e exibi-la condicionalmente. Para fazer isso, precisamos quebrar a lista de exibição de sentimentos. Renomeie SentimentDisplay para Display e vamos dividir a lista de exibição de sentimentos:
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 como estamos usando uma das definições de classe na interface CuratedTweets. Isso porque esses componentes não precisam de todo o objeto CuratedTweets, mas apenas de um subconjunto. A lista de tópicos é muito semelhante:
interface TopicDisplayProps { groupedByNP: Record<string, Array<TweetRecord>> } const TopicDisplay: React.FC<TopicDisplayProps> = ({groupedByNP}) => { return <IonList> {Object.keys(groupedByNP || {}).map((x, i) => <IonItem key={i} routerLink={`/topicDisplay/${encodeURIComponent(x)}`}> <IonLabel className="ion-text-wrap"> <h2>{x} ({groupedByNP[x].length})</h2> </IonLabel> </IonItem>)} </IonList> }
E agora, a exibição condicional é fácil de configurar no Componente de exibição:
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> );
Certifique-se de alterar a exportação padrão e agora estamos prontos para implementar a página de exibição de tópicos.
Página de exibição do tópico
Plano geral
A página de exibição de tópicos é uma exibição de lista semelhante à exibição de sentimentos, mas procuraremos o tópico em questão a partir do parâmetro de rota.
Configuração de rota
Se você chegou até aqui, já deve saber o que fazer. Vamos criar uma pasta de página chamada topicdisplay e um TopicDisplay.tsx, escrever um componente stub e importá-lo para a página App.tsx. Agora vamos configurar as rotas:
<Route path="/topicDisplay/:topic" render={() => <TopicDisplay curatedTweets={curatedTweets} /> } />
Agora estamos prontos para implementar o componente de interface do usuário.
Componentes de IU
Primeiro, vamos criar a definição ContainerProps
:
interface ContainerProps { curatedTweets: CuratedTweets } const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { Return <div>Topic Display</div> }
Agora, precisaremos recuperar o tópico do nome do caminho da URL. Para fazer isso, usaremos a API de histórico. Então, vamos importar useHistory
, instanciar a API de histórico e extrair o tópico do nome do caminho. Enquanto estamos nisso, vamos também implementar a funcionalidade de retorno:
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];
Agora que temos os tweets com esse tópico em particular, a exibição é bem simples:
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> );
Salve e corra, e as coisas devem parecer boas.
Executando o aplicativo no emulador
Para executar o aplicativo no emulador, basta executar alguns comandos Ionic para adicionar a plataforma móvel e copiar o código, semelhante a como configuramos as coisas com o Cordova.
ionic build # builds the app ionic cap add ios # adds iOS as one of the platforms, only have to run once ionic cap copy # copy the build over ionic cap sync # only need to run this if you added native plugins ionic cap open ios # run the iOS emulator
E você deve ver o aplicativo aparecer.
Implementação nativa do React
React Native Primer
O React Native tem uma abordagem muito diferente das abordagens baseadas na web das seções anteriores. React Native renderiza seu código React como componentes nativos. Isso vem com várias vantagens. Primeiro, a integração com o sistema operacional subjacente é muito mais profunda, o que permite ao desenvolvedor aproveitar os novos recursos do smartphone e recursos específicos do sistema operacional que podem não estar disponíveis por meio do Cordova/Capacitor. Segundo, porque não há um mecanismo de renderização baseado na Web no meio, um aplicativo React Native geralmente é mais rápido do que um escrito usando Cordova. Finalmente, como o React Native permite a integração de componentes nativos, os desenvolvedores podem exercer um controle muito mais refinado sobre seus aplicativos.
Para nosso aplicativo, usaremos a lógica das seções anteriores e usaremos uma biblioteca de componentes React Native chamada NativeBase para codificar nossa interface do usuário.
Configurando o aplicativo
Primeiro, você desejará instalar todos os componentes necessários do React Native seguindo as instruções aqui.
Uma vez instalado o React Native, vamos iniciar o projeto:
react-native init TwitterCurationRN
Deixe o script de configuração ser executado e, eventualmente, a pasta deverá ser criada. Cd na pasta e execute react-native run-ios, e você deverá ver o emulador aparecer com o aplicativo de exemplo.
Também queremos instalar o NativeBase, pois essa é nossa biblioteca de componentes. Para isso, executamos:
npm install --save native-base react-native link
Também queremos instalar o navegador de pilha React Native. Vamos correr:
npm install --save @react-navigation/stack @react-navigation/native
E
react-native link cd ios pod-install cd
Para concluir a vinculação e instalação dos plugins nativos.
Configuração do roteador
Para roteamento, usaremos o navegador de pilha que instalamos na etapa acima.
Importe os componentes do roteador:
import { NavigationContainer } from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack';
E agora, criamos um navegador de pilha:
const Stack = createStackNavigator();
Altere o conteúdo do componente App para usar o navegador de pilha:
const App = () => { return ( <> <StatusBar bar /> <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> </Stack.Navigator> </NavigationContainer> </> ); };
Neste ponto, você receberá um erro porque a entrada ainda não está definida. Vamos definir um elemento stub apenas para torná-lo feliz.
Crie uma pasta de componentes em seu projeto, crie um arquivo chamado Entry.jsx e adicione um componente stub da seguinte forma:
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 o componente Entry em seu aplicativo e ele deve ser compilado.
Agora, estamos prontos para codificar a página de entrada.
Página de entrada
Plano geral
Estaremos implementando uma página muito semelhante à implementada acima, mas usando componentes NativeBase. A maioria das APIs JavaScript e React que usamos, como hooks e fetch, ainda estão disponíveis.
A única diferença será a forma como trabalhamos com a API de navegação, que você verá mais tarde.
Componentes de IU
Os componentes NativeBase adicionais que usaremos são Container, Content, Input, List, ListItem e Button. Todos eles têm análogos no Ionic e no Bootstrap React, e os construtores do NativeBase o tornaram muito intuitivo para pessoas familiarizadas com essas bibliotecas. Basta importar assim:
import { Container, Content, Input, Item, Button, List, ListItem, Text } from 'native-base';
E o componente é:
return <Container> <Content> <Item regular> <Input placeholder='Input Handles Here' onChangeText={inputChange} value={input} /> <Button primary onPress={onAddClicked}><Text> Add </Text></Button> <Text> </Text> <Button success onPress={onPullClicked}><Text> Pull </Text></Button> </Item> <Item> <List style={{width: '100%'}}> {handles.map((item) => <ListItem key={item.key}> <Text>{item.key}</Text> </ListItem>)} </List> </Item> </Content> </Container>
E agora, vamos implementar os manipuladores de estado e evento:
const [input, changeInput] = useState(''); const [handles, changeHandles] = useState([]); const inputChange = (text) => { changeInput(text); } const onAddClicked = () => { const newHandles = [...handles, {key: input}]; changeHandles(newHandles); changeInput(''); }
E, finalmente, a chamada da 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); }) }
Observe que essa implementação é a mesma da implementação NativeBase, exceto que estamos navegando de uma maneira diferente. O navegador de pilha passa para seus componentes uma prop chamada “navigation” e que pode ser usada para navegar entre rotas com .navigate
. Além de simplesmente navegar, você também pode passar dados para o componente de destino. Usaremos esse mecanismo para passar os dados.
Para fazer o aplicativo compilar, ainda precisamos tornar o Entry
ciente do componente de navegação. Para fazer isso, precisaremos alterar a declaração da função do componente:
export default Entry = ({navigation}) => {
Agora salve, e você deverá ver a página.
Página classificada de sentimento
Plano geral
Implementaremos a página de sentimento de maneira semelhante às seções anteriores, mas estilizaremos a página de maneira um pouco diferente e também usaremos a biblioteca de navegação de maneira diferente para obter o valor de retorno da chamada da API.
Como o React Native não possui CSS, precisaremos definir um objeto StyleSheet ou simplesmente codificar os estilos in-line. Como compartilharemos alguns dos estilos entre os componentes, vamos criar uma folha de estilos global. Faremos isso após a configuração da rota.
Além disso, como StackNavigator
possui um botão de navegação Voltar embutido, não precisaremos implementar nosso próprio botão Voltar.
Configuração de rota
A definição de rota no StackNavigator
é muito simples. Nós simplesmente criamos um novo chamado Stack Screen e damos a ele o componente, muito parecido com o roteador React.
<NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> </Stack.Navigator> </NavigationContainer>
Para fazer isso funcionar, é claro que precisaremos criar um componente stub em components/SentimentDisplay.js:
import React from 'react'; import {Text} from 'native-base'; const SentimentDisplay = () => { return <Text>Sentiment Display</Text>; } export default SentimentDisplay;
E importe:
import SentimentDisplay from './components/SentimentDisplay';
Agora, estamos prontos para criar a folha de estilo global.
Folha de estilo global
Primeiro, crie um arquivo chamado globalStyles.js. Em seguida, importe o componente StyleSheet do React Native e defina os estilos:
import {StyleSheet} from 'react-native'; export default StyleSheet.create({ tweet: {paddingTop: 5}, accountName: {fontWeight: '600'}, })
E estamos prontos para codificar a interface do usuário.
Componentes de IU
O componente de interface do usuário é bastante familiar, com exceção de como trabalhamos com as rotas. Queremos usar a navegação e a rota de props especiais do StackNavigator
para obter o estado atual do aplicativo e navegar até a exibição do tópico, caso o usuário queira ver essa página.
Altere a definição do componente para acessar as props de navegação:
const SentimentDisplay = ({route, navigation}) => {
E agora, implementamos a leitura do estado do aplicativo e a funcionalidade de navegação:
const {params: {data}} = route; const viewByTopicClicked = () => { navigation.navigate('TopicDisplay', { data }); }
Importe os estilos globais:
import globalStyles from './globalStyles';
E os componentes:
import { View } from 'react-native'; import {List, Item, Content, ListItem, Container, Text, Button} from 'native-base';
E por fim, os 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>;
Salve e tente puxar alguns tweets, e você verá a exibição de sentimento. Agora na página de agrupamento de tópicos.
Página de agrupamento de tópicos
Plano geral
A exibição de tópicos é novamente muito semelhante. Usaremos um construtor de manipulador para construir uma função de navegação para navegar até a página de exibição de um item de tópico específico e também definiremos uma folha de estilo específica para esta página.
Uma coisa nova que faremos é implementar um TouchableOpacity, que é um componente específico do React Native que funciona como um botão.
Configuração de rota
A definição de rota é a mesma de 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>
Os componentes do componente de stub/TopicDisplay.js:
import React from 'react'; import {Text} from 'native-base'; const TopicDisplay = () => { return <Text>Topic Display</Text>; } export default TopicDisplay;
E importe:
import TopicDisplay from './components/TopicDisplay;
Componentes de IU
Muito disso parecerá muito familiar. Importe as funções da biblioteca:
import { View, TouchableOpacity, StyleSheet } from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base';
Importe os estilos globais:
import globalStyles from './globalStyles';
Defina os estilos personalizados:
const styles = StyleSheet.create({ topicName: {fontWeight: '600'}, })
Defina os adereços de navegação:
export default TopicDisplay = ({route, navigation}) => {
Defina os manipuladores de dados e ações. Observe que estamos usando um construtor de manipulador, uma função que retorna uma função:
const {params: {data}} = route; const specificItemPressedHandlerBuilder = (topic) => () => { navigation.navigate('TopicDisplayItem', { data, topic }); }
E agora, os componentes. Observe que estamos usando um TouchableOpacity, que pode ter um manipulador onPress
. Poderíamos ter usado TouchableTransparency também, mas a animação de clicar e segurar do TouchableOpacity foi mais adequada para nosso aplicativo.
return <Container> <Content> <Item> <List style={{width: '100%'}}> {Object.keys(data.groupedByNp).map((topic, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <TouchableOpacity onPress={specificItemPressedHandlerBuilder(topic)}> <Text style={styles.topicName}>{topic}</Text> </TouchableOpacity> </View> </ListItem>)} </List> </Item> </Content> </Container>;
E isso deve fazê-lo. Salve e experimente o aplicativo!
Agora, na Página de Item de Exibição de Tópico.
Página de item de exibição de tópico
Plano geral
A página de item de exibição de tópico é muito semelhante e todas as idiossincrasias são atendidas nas outras seções, portanto, deve ser fácil navegar a partir daqui.
Configuração de rota
Adicionaremos a definição de rota:
<Stack.Screen name="TopicDisplayItem" component={TopicDisplayItem} />
Adicione a importação:
import TopicDisplayItem from './components/TopicDisplayItem';
E crie o componente stub. Em vez de apenas um componente simples, vamos também importar os componentes NativeBase que usaremos e definir as props de rota:
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 IU
O componente de interface do usuário é bastante simples. Já vimos isso antes e não estamos realmente implementando nenhuma lógica personalizada. Então, vamos em frente! Respire fundo…
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>;
Salve, e devemos estar prontos para ir! Agora estamos prontos para executar o aplicativo no emulador... espere, já não fizemos?
Executando o aplicativo
Bem, como você está trabalhando com React Native, você já está executando o aplicativo no emulador, então essa parte já está resolvida. Essa é uma das grandes vantagens do ambiente de desenvolvimento do React Native.
Uau! Com isso, terminamos a parte de codificação deste artigo. Vamos ver o que aprendemos sobre as tecnologias.
Comparando as tecnologias
Córdoba: prós e contras
A melhor coisa sobre o Cordova é a velocidade com que um desenvolvedor web habilidoso pode codificar algo funcional e razoavelmente apresentável. As habilidades de desenvolvimento da Web e a experiência são transferidas facilmente porque, afinal, você está codificando um aplicativo da Web. O processo de desenvolvimento é rápido e simples, e o acesso à API do Cordova também é simples e intuitivo.
As desvantagens de usar o Cordova diretamente vêm principalmente da dependência excessiva de componentes da web. Os usuários esperam uma experiência de usuário específica e um design de interface ao usar aplicativos móveis, e quando um aplicativo parece um site móvel, a experiência pode ser um pouco chocante. Além disso, a maioria dos recursos integrados aos aplicativos, como animações de transição e utilitários de navegação, devem ser implementados manualmente.
Iônico: prós e contras
A melhor parte do Ionic foi a quantidade de recursos centrados em dispositivos móveis que recebi “de graça”. Ao codificar da mesma forma que codificaria um aplicativo da Web, consegui criar um aplicativo que parece muito mais compatível com dispositivos móveis do que simplesmente usar Cordova e React-Bootstrap. Havia animação de navegação, botões com estilos de aparência nativa e muitas opções de interface do usuário que tornavam a experiência do usuário muito suave.
A desvantagem de usar o Ionic foi parcialmente causada por seus pontos fortes. Primeiro, às vezes era difícil imaginar como o aplicativo se comportaria em vários ambientes. Só porque o aplicativo parecia de uma maneira não significava que o mesmo posicionamento da interface do usuário teria a mesma aparência em outro ambiente. Em segundo lugar, o Ionic fica no topo de muitas tecnologias subjacentes, e obter acesso a alguns dos componentes provou ser difícil. Por fim, isso é específico do Ionic-React, mas como o Ionic foi construído pela primeira vez para o Angular, muitos recursos do Ionic-React pareciam ter menos documentação e suporte. No entanto, a equipe Ionic parece muito atenta às necessidades dos desenvolvedores do React e entrega novos recursos rapidamente.
Reagir nativo: prós e contras
O React Native teve uma experiência de usuário muito tranquila desenvolvendo em dispositivos móveis. Ao conectar-se diretamente ao emulador, não era nenhum mistério como o aplicativo ficaria. A interface do depurador baseada na web foi extremamente útil na aplicação cruzada de técnicas de depuração do mundo da aplicação web, e o ecossistema é bastante robusto.
A desvantagem do React Native vem de sua proximidade com a interface nativa. Muitas bibliotecas baseadas em DOM não podiam ser usadas, o que significava ter que aprender novas bibliotecas e melhores práticas. Sem o benefício do CSS, o estilo do aplicativo era um pouco menos intuitivo. Por fim, com muitos novos componentes para aprender (por exemplo, View em vez de div, componente Text envolvendo tudo, Buttons vs. TouchableOpacity vs. TouchableTransparency, etc.), há um pouco de curva de aprendizado no início se alguém estiver entrando no Reaja o mundo nativo com pouco conhecimento prévio da mecânica.
Quando usar cada tecnologia
Como Cordova, Ionic e React Native têm prós e contras muito fortes, cada tecnologia tem um contexto no qual desfrutaria da melhor produtividade e desempenho.
Se você já tem um aplicativo existente que é o primeiro na web com uma forte identidade de marca em torno do design da interface do usuário e aparência geral, sua melhor opção seria o Cordova, que oferece acesso aos recursos nativos do smartphone e permite reutilizar a maior parte de seus componentes da web e preserve a identidade da sua marca no processo. Para aplicativos relativamente simples usando uma estrutura responsiva, você pode criar um aplicativo móvel com poucas alterações necessárias. No entanto, seu aplicativo se parecerá menos com um aplicativo e mais com uma página da Web, e alguns dos componentes que as pessoas esperam de um aplicativo móvel serão codificados separadamente. Portanto, eu recomendo o Cordova nos casos em que você está em um projeto web-first portando o aplicativo para dispositivos móveis.
Se você está começando a codificar um novo aplicativo com uma filosofia app-first, mas o conjunto de habilidades de sua equipe é principalmente em desenvolvimento web, recomendo o Ionic. A biblioteca do Ionic permite que você escreva rapidamente um código que pareça e se pareça com os componentes nativos, permitindo que você aplique suas habilidades e instintos como desenvolvedor web. Descobri que as práticas recomendadas de desenvolvimento da Web se aplicavam prontamente ao desenvolvimento com o Ionic, e o estilo com CSS funcionava perfeitamente. Além disso, a versão móvel do site parece muito mais nativa do que um site codificado usando uma estrutura CSS responsiva. No entanto, em algumas etapas do caminho, descobri que a integração da API React-Ionic-Native requer algum ajuste manual, o que pode ser demorado. Portanto, recomendo o Ionic nos casos em que seu aplicativo está sendo desenvolvido desde o início e você deseja compartilhar uma quantidade significativa de código entre um aplicativo da Web compatível com dispositivos móveis e um aplicativo móvel.
Se você estiver codificando um novo aplicativo com alguma base de código nativa implementada, experimente o React Native. Mesmo se você não estiver usando código nativo, também pode ser a melhor opção nos casos em que você já está familiarizado com o React Native ou quando sua principal preocupação é o aplicativo móvel em vez de um aplicativo híbrido. Tendo focado a maior parte dos meus esforços de desenvolvimento front-end no desenvolvimento web, inicialmente descobri que começar com React Native tinha mais curva de aprendizado do que Ionic ou Cordova devido às diferenças na organização de componentes e convenções de codificação. No entanto, uma vez que essas nuances são aprendidas, a experiência de codificação é bastante suave, especialmente com a ajuda de uma biblioteca de componentes como NativeBase. Dada a qualidade do ambiente de desenvolvimento e o controle sobre o aplicativo, se o front-end do seu projeto for principalmente um aplicativo móvel, eu recomendaria o React Native como sua ferramenta de escolha.
Tópicos futuros
Um dos tópicos que não tive tempo de explorar foi a facilidade de acesso a APIs nativas como câmera, geolocalização ou autenticação biométrica. Um dos grandes benefícios do desenvolvimento móvel é a acessibilidade de um rico ecossistema de API que geralmente não é acessível no navegador.
Em artigos futuros, gostaria de explorar a facilidade de desenvolver esses aplicativos nativos habilitados para API usando várias tecnologias de plataforma cruzada.
Conclusão
Hoje, implementamos um aplicativo de curadoria do Twitter usando três tecnologias de desenvolvimento móvel multiplataforma diferentes. Espero que isso tenha lhe dado uma boa noção de como é cada tecnologia e inspirado você a desenvolver seu próprio aplicativo baseado em React.
Obrigado por ler!