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

Publicados: 2022-03-11

Nos 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!