As criptomoedas estão com tudo atualmente e neste tutorial eu vou juntar elas com outra de minhas paixões que é o desenvolvimento mobile. Eu não sei se você sabe mas minha pós-graduação foi neste assunto, em 2012-13 e anos mais tarde o primeiro livro que escrevi também foi sobre desenvolvimento mobile, o Criando apps para Empresas com Android. Já criptomoedas entraram na minha vida em 2017, no bull run que o Bitcoin teve naquela época e quando escrevi originalmente este tutorial de bot cripto.
Para o tutorial de hoje eu quero criar com você um app de criptomoedas que funcione em Android e iOS, ajudando você a acompanhar o mercado. Nada muito elaborado, mas que sirva de primeiros passos para algo inédito e mais bacana que você tenha a ideia de construir. Usaremos aqui a tecnologia React Native, que permite criar apps nativos para estas duas plataforma usando apenas a linguagem JavaScript.
Não apenas isso, usaremos o pacote Expo para React Native, uma plataforma que simplifica bastante o desenvolvimento e principalmente os testes e build e sem a necessidade de ter um Mac para isso.
Eu ensino o básico de React Native e de Expo nesta série de tutoriais que é leitura obrigatória para conseguir acompanhar este tutorial.
Dito isso, vamos ao tutorial. Se preferir, você pode assistir ele no vídeo abaixo ao invés de ler (tem pouca variação, o post está mais atualizado).
1 – Setup do Projeto
O primeiro passo é você criar seu projeto usando o Expo. Para isso, use o comando abaixo em uma pasta na sua máquina, escolhendo o template Blank com JavaScript quando for questionado.
|
1 2 3 |
npx create-expo-app cryptowatch --template |
Eu chamei meu projeto de CryptoWatch, pois é isso que basicamente ele vai ser, um “observador de criptos”, mas renomeie conforme julgar melhor.
Após todas as dependências serem baixadas e instaladas, convém entrar na pasta cryptowatch e rodar o projeto para ver se deu tudo certo na criação.
|
1 2 3 |
npm start |
Como quero receber as informações em tempo real sobre a cripto que estou interessado, vamos usar as streams da Binance para isso. A Binance é a maior exchange do mundo e suas informações são públicas e facilmente acessíveis através de websockets, que eu já expliquei outras vezes aqui no blog como funciona. Para isso, no mundo web eu utilizo o pacote react-use-websocket mas como ele tem dependências do React DOM, o que não agrega em nada para o app, eu criei uma variação dele usando as APIs nativas de websockets, que você deve copiar abaixo e salvar em um arquivo useWebSocket.js com os demais.
Se não quiser usar o código abaixo, basta instalar react-use-websocket e react-dom no seu projeto que a forma de usar depois é a mesma.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
import { useEffect, useRef, useState } from "react"; /** * Hook genérico para conexão WebSocket com reconexão e controle de estado * * @param {string} url - URL completa do WebSocket * @param {object} options - Opções de configuração * @param {function} options.onMessage - Callback ao receber mensagem * @param {function} options.onOpen - Callback ao abrir conexão * @param {function} options.onError - Callback ao ocorrer erro * @param {function|boolean} options.shouldReconnect - Se deve reconectar automaticamente * @param {number} options.reconnectInterval - Intervalo entre reconexões (ms) * @param {number} options.maxRetries - Número máximo de tentativas de reconexão (-1 para ilimitado) */ export default function useWebSocket(url, options = {}) { const { onMessage, onOpen, onError, shouldReconnect = true, reconnectInterval = 3000, maxRetries = -1, } = options; const [lastMessage, setLastMessage] = useState(null); const [readyState, setReadyState] = useState("closed"); // 'connecting' | 'connected' | 'reconnecting' | 'closed' const [retryCount, setRetryCount] = useState(0); const wsRef = useRef(null); const reconnectTimeout = useRef(null); useEffect(() => { if (!url) return; let isMounted = true; const connect = () => { setReadyState(retryCount > 0 ? "reconnecting" : "connecting"); const ws = new WebSocket(url); wsRef.current = ws; ws.onopen = () => { if (!isMounted) return; setReadyState("connected"); setRetryCount(0); onOpen && onOpen(); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); setLastMessage(data); onMessage && onMessage(data); } catch (err) { console.error("Erro ao processar mensagem WebSocket:", err); } }; ws.onerror = (err) => { console.error("WebSocket erro:", err.message || err); onError && onError(err); }; ws.onclose = () => { if (!isMounted) return; setReadyState("closed"); const reconnect = typeof shouldReconnect === "function" ? shouldReconnect() : shouldReconnect; if (reconnect && (maxRetries === -1 || retryCount < maxRetries)) { console.warn( `WebSocket desconectado. Tentando reconectar (${retryCount + 1})...` ); reconnectTimeout.current = setTimeout(() => { setRetryCount((prev) => prev + 1); connect(); }, reconnectInterval); } }; }; connect(); return () => { isMounted = false; if (reconnectTimeout.current) clearTimeout(reconnectTimeout.current); if (wsRef.current) wsRef.current.close(); }; }, [url]); const sendMessage = (msg) => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { const payload = typeof msg === "string" ? msg : JSON.stringify(msg); wsRef.current.send(payload); } else { console.warn("WebSocket não está aberto, mensagem não enviada."); } }; return { sendMessage, lastMessage, readyState, // 'connecting' | 'connected' | 'reconnecting' | 'closed' retryCount, }; } |
E para criar a interface do nosso app eu gosto bastante da biblioteca React Native Elements, que é tipo um Bootstrap do mundo React Native, muito prático de usar e com uma aparência bem bacana. Mas ao invés de instalar o projeto original, eu recomendo o fork Vikalp, que está mais atualizado, que você consegue instalar com o comando abaixo. Também vou instalar o pacote de ícones do Expo.
|
1 2 3 |
npx expo install @rn-vui/base @rn-vui/themed @expo/vector-icons |
Agora sim, temos as dependências que vamos precisar!
2 – Conectando na Stream
A primeira coisa que vamos fazer é a que o pessoal costuma errar mais que é na conexão à stream de websockets da Binance. Para fazer isso vamos importar o hook que criamos e vamos usá-lo para estabelecer a conexão, inicialmente fixa no par BTCUSDT.
Isso deve ser feito no App.js, o arquivo central da nossa aplicação. Usaremos um state para que a cada atualização de dados o nosso front reaja exibindo o que recebeu.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import { Text, View } from 'react-native'; import {useState} from 'react'; import useWebSocket from './useWebSocket'; export default function App() { const [data, setData] = useState({}); useWebSocket(`wss://stream.binance.com:9443/ws/btcusdt@ticker`, { onOpen: () => { }, onMessage: (message) => { if(!message) return; setData({ priceChange: parseFloat(message.p), priceChangePercent: parseFloat(message.P), close: message.c, high: message.h, low: message.l, quoteVolume: message.q, numberOfTrades: message.n }); }, onError: (event) => console.error(event), shouldReconnect: (closeEvent) => true, reconnectInterval: 3000 }); return ( <View> <Text>{JSON.stringify(data)}</Text> </View> ); } |
A configuração mais importante do hook useWebSocket é a URL onde ele vai se conectar e a função onMessage que vai ser disparada toda vez que ele receber um dado atualizado, o que vem via o objeto message. Mais à frente, vamos fazer ele se conectar na stream de dados da cripto que interessa ao usuário, ao invés de deixá-la fixa como fizemos.
O resultado é que quando você abrir o app ele vai se conectar na Binance e passar a receber os dados atualizados de uma cripto a cada segundo, que estamos apenas colocando na tela, como abaixo.

Cada uma dessas informações retornadas pela Binance é melhor descrita na documentação oficial deles, mas tem muita coisa de valor aí que podemos usar para construir uma tela bacana. Não consegue entender a documentação? O vídeo abaixo vai ajudar.

Agora que aprendemos a fazer a conexão, vamos construir a nossa tela!
#3 – Input de Dados
Eu sou péssimo em frontend. Não me leve à mal, eu sei construir, mas falando de criatividade, de design, eu sou uma negação, hehe. Por isso que gosto de bibliotecas como Bootstrap para a web e para o React Native eu costumo usar o React Native Elements, que comentei e que deixamos instalado anteriormente. Agora vamos usá-lo!
Ainda no app.js, vamos ajustar a tela para que tenhamos um campo de texto e um botão. O usuário digitará o par de moedas no campo e pressionará o botão, mudando a conexão para a nova stream. Não vou colocar a conexão enquanto ele digita para não ficar fazendo conexões erradas na Binance, o que poderia prejudicar a reputação do nosso IP.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import { View } from 'react-native'; import { useState } from 'react'; import useWebSocket from './useWebSocket'; import { Input, Text } from '@rn-vui/themed'; import { Feather as Icon } from '@expo/vector-icons'; export default function App() { const [text, setText] = useState('BTCUSDT'); const [symbol, setSymbol] = useState('btcusdt') const [data, setData] = useState({}); //...código do useWebSocket permanece aqui... return ( <View style={styles.container}> <Text style={styles.h1}>CryptoWatch 1.0</Text> <Input title="Digite o par de moedas." autoCapitalize='characters' leftIcon={<Icon name='dollar-sign' size={24} color='black' />} value={text} rightIcon={<Icon.Button name="search" size={24} color='black' backgroundColor='transparent' onPress={evt => setSymbol(text.toLowerCase())} />} onChangeText={txt => setText(txt.toUpperCase())} /> <Text>{JSON.stringify(data)}</Text> </View> ); } |
Primeiro vamos falar das novas importações: trouxe o Input e o Text da biblioteca React Native Elements Vikalp e também o Feather, que renomeei para Icon, da biblioteca interna do Expo. Por padrão o Expo oferece várias bibliotecas de ícones pra gente e a Feather Icons é uma das mais populares. Vamos usá-los em nosso projeto.
Adicionamos agora dois novos states, um para o texto que o usuário estará digitando e outro para o symbol (par de moedas) já digitado, e que é usado para fazer a conexão no lugar certo (repare a URL no useWebSocket). Importante frisar também que colocamos valores default nesses states.
E a última alteração e mais drástica foi no JSX de retorno da interface, onde coloquei um Text com estilo h1 para ser o título da tela (código do estilo a seguir), coloquei um input com ícone na esquerda e ícone clicável na direita (já vou falar dele) e logo abaixo ficou o texto que já tínhamos antes.
O ícone clicável à direita do Input serve como botão de confirmação do texto digitado. Enquanto o usuário vai digitando, a propriedade onChangeText vai disparando a atualização do state text. Quando o botão é clicado, o onPress do Icon.Button é disparado, alterando o state de symbol.
E como em toda boa aplicação React, quando um state muda, o componente é renderizado novamente.
Repare também que adicionei estilos em alguns componente, usando a propriedade style. Essa propriedade funciona de maneira idêntica ao CSS do mundo web, que se você não conhece, recomendo este ebook gratuito. Para os estilos, você deve importar o StyleSheet do pacote react-native e criar um objeto styles definindo os estilos citados acima, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({ container: { flexDirection: "column", marginTop: 40, margin: 20, alignContent: "center" }, h1: { fontSize: 24, fontWeight: "bold" }, }) |
O resultado atualizado você confere abaixo. Experimente digitar outro par e clicar na lupa e verá que as informações abaixo passam a corresponder ao novo par.

#4 – Listagem das Informações
Agora vamos para a última parte do nosso app, onde vamos pegar aquelas informações que estão chegando e que hoje jogamos de qualquer jeito na tela e vamos transformar em uma tabela mais amigável para o usuário ler e entender.
Usaremos aqui uma combinação de componentes Text, já que são textos alterados via programação, sem interferência do usuário. A base é essa aqui e será repetida à exaustão:
|
1 2 3 4 5 6 |
<View style={styles.line}> <Text style={styles.bold}>Preço Atual: </Text> <Text>{data.close}</Text> </View> |
No exemplo acima, temos uma view com um estilo de linha (que vou mostrar abaixo), um text com estilo negrito pro rótulo da informação (estilo bold) e outro text com a informação que vem da stream (sem estilo algum).
Ajuste os estilos ao final do arquivo, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const styles = StyleSheet.create({ container: { flexDirection: "column", marginTop: 40, margin: 20, alignContent: "center" }, line: { flexDirection: 'row', width: '100%', marginHorizontal: 10, marginVertical: 10 }, bold: { fontWeight: 'bold' }, h1: { fontSize: 24, fontWeight: "bold" }, }) |
Agora, o bloco completo com todas as tags para composição das informações na tela.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<View style={styles.line}> <Text style={styles.bold}>Preço Atual: </Text> <Text>{data.close}</Text> </View> <View style={styles.line}> <Text style={styles.bold}>Alteração: </Text> <Text>{getSignal(data.priceChange)} ({getSignal(data.priceChangePercent)}%)</Text> </View> <View style={styles.line}> <Text style={styles.bold}>Máxima 24h: </Text> <Text>{data.high}</Text> </View> <View style={styles.line}> <Text style={styles.bold}>Mínima 24h: </Text> <Text>{data.low}</Text> </View> <View style={styles.line}> <Text style={styles.bold}>Trades: </Text> <Text>{data.numberOfTrades}</Text> </View> <View style={styles.line}> <Text style={styles.bold}>Volume: </Text> <Text>{data.quoteVolume}</Text> </View> |
Repare que acima tem dois campos que eu uso uma função getSignal para colocar um sinal de positivo ou negativo no número. Esta função está abaixo e é bem simples. Aliás, é por causa desse ajuste que quando recebemos os campos de preço eu faço um parseFloat lá no useWebSocket.
|
1 2 3 4 5 |
function getSignal(value) { return value >= 0 ? `+${value}` : value; } |
O resultado você confere abaixo.

E com isto finalizamos este tutorial. Se você fez as outras séries de React Native que tem aqui no blog como a básica e a do CRUD agora sua cabeça está fervendo com as possibilidades. Abaixo eu listo alguns pacotes famosos que podem lhe ajudar na criação de mais funcionalidades ou mesmo de outros apps ligados a este mundo de criptomoedas:
- EthersJS: para dapps web3 que se conecta na blockchain;
- Axios: para consumir as APIs REST da Binance, de outras corretoras ou de backends próprios;
- OpenAI: para criar funcionalidades que usem IA;
E nesse mundo de apps ainda, aqui no blog ensino como fazer dinheiro com apps.

Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.



