-
Componentes visuais do React Native são compilados nativamente para as respectivas plataformas (iOS, Android, etc...)
-
Lógica de negócio permanece em JavaScript
- Criar o projeto (
npx create-expo-app ControleGastos
)cd ControleGastos npm run android
- Pasta
assets
pode ser utilizada para armazenar imagens utilizadas pelo app - Arquivo
App.js
contém o código fonte - Principais blocos:
- import
- código
- estilos
- Função
App()
indica o ponto de entrada do app - O retorno desta função deve representar o desenho da interface de usuário que será convertida para eleentos nativos na plataforma específica (ios, android, web, etc...)
export default function App() { return null; }
- Lista dos Componentes Visuais e APIs
- Importar
import { Text } from 'react-native';
- Exibe um texto estático
- Podem ser utlizadas expressões dentro do texto
- Para processar expressões é necessário utilizar os símbolos
{
e}
- Por exemplo:
export default function App() { var variavel = 'Ok!' return <Text>Tudo certo = {variavel}, 2 + 2 = {2 + 2}</Text> }
- Estilos podem ser aplicados localmente
style={{margin: 100}}
export default function App() { var variavel = 'Ok!' return <Text style={{margin: 100}}>Tudo certo = {variavel}, 2 + 2 = {2 + 2}</Text> }
- Importar
import { View } from 'react-native';
- Representa um container para demais componentes React onde estilos podem ser aplicados globalmente
export default function App() { var variavel = 'Ok!' const texto = () => { return "Boa noite!"; } return <View style={{margin: 100, borderWidth: 5}}> <Text>Tudo certo = {variavel}</Text> <Text>2 + 2 = {2 + 2}</Text> <Text>{texto()}</Text> </View> }
- Importar
import { StyleSheet } from 'react-native';
- Utilizar o método utilitário
StyleSheet.create
para criar objetos de estilo - São "semelhantes" ao css mas não são iguais!!!!
const styles = StyleSheet.create({ container: { flex: 1, // ocupa toda a dimensão vertical marginTop: '10%', borderWidth: 5, borderColor: 'red' }});
- Referenciar com a propriedade
style={objeto_estilo.nome_estilo}
export default function App() { var variavel = 'Ok!' return <View style={styles.container}> <Text>Tudo certo = {variavel}</Text> <Text>2 + 2 = {2 + 2}</Text> </View> }
- Controle de estado é um conceito fundamental em React
- Estado representa o valor das variáveis em um determinado momento
- Caso o estado de uma variável seja alterado internamente pode-se desejar que esta alteração seja refletida na interface de usuário
export default function App() { let contador = 0; const contar = () => { contador++; } return <View style={styles.container}> <Text style={{textAlign: 'center'}}>{contador}</Text> <Button title='Contar' onPress={contar}/> </View> }
- O exemplo acima não funciona, o contador não é atualizado na interface
- Para que isso seja feito é necessário o uso do
useState
import { useState } from 'react';
// variável contador somente leitura
// para alterar seu valor utilizar a função incContador
const [contador, incContador] = useState(0);
const contar = () => {
// atribuir o valor atual de contador a outra variável
let newContador = contador;
// atualizar o novo valor utilizando o incContador
incContador(++newContador);
}
- Importar
import { TextInput } from 'react-native';
- Permite a entrada de um texto
- Deve ser associado a uma variável de estado
useState
- Principais propriedades:
style
- define o estiloonChangeText
- associado à função de controle de estado (useState
)value
- valor digitado e associado à variável de estado (useState
)
- Importar controle de estado do React:
import { useState } from 'react';
- Criar uma variável de estado chamada
descricaoGasto
const [descricaoGasto, onChangeDescricaoGasto] = useState('');
- Criar um manipulador para o texto inserido:
const descricaoGastoHandler = (texto) => { console.log(texto); onChangeDescricaoGasto(texto); }
- Definir o
<TextInput>
:<TextInput style={styles.input} value={descricaoGasto} onChangeText={descricaoGastoHandler} placeholder="Descrição do Gasto"/>
- Definir o estilo:
input: { height: 40, margin: 12, width: 300, borderWidth: 1, padding: 10, },
- Criar o
<Text>
:<Text>{descricaoGasto}</Text>
- Importar
import { Button } from 'react-native';
- Botão que pode ser pressionado
- Principais propriedades:
style
- define o estiloonPress
- função acionada quando o botão é pressionadotitle
- rótulo do botão
- Criar um
<Button>
<Button title="Adicionar" onPress={addDescricaoGastoHandler}/>
- Criar um manipulador a ser acionado quando o botão for pressionado:
const addDescricaoGastoHandler = () => { console.log(descricaoGasto); }
- Para configurar margens, altura, etc... deve-se colocar o botão dentro de uma
<View>
<View style={[{ width: "90%", margin: 2, backgroundColor: "red" }]}> <Button title='Incrementar' onPress={incrementarValor} /> </View>
- Outra opção é utilizar o
<TouchableOpacity>
<TouchableOpacity style={{ backgroundColor: "#ABAB", height: 50 }}> <Text>Clique Aqui</Text> </TouchableOpacity>
- Nota: como concatenar arrays em Javascript utilizando spread (
...
)const letras = ["A", "B", "C"]; const maisLetras = [letras, "D"]; console.log(maisLetras);
- Resultado:
[["A", "B", "C"], "D"]
- um array dentro de outroconsole.log(...letras)
- Concatenação com o spread (
...
):const maisLetras = [...letras, "D"]; console.log(maisLetras)
- Nota: utilizando
map
para percorrer elementos de um arraymaisLetras.map((item) => console.log(item))
- Criar uma variável de estado para armazenar a lista de gastos:
const [listaGastos, setListaGastos] = useState([]);
- Alterar a função acionada quando o botão é pressionado:
const addDescricaoGastoHandler = () => { setListaGastos((gastosAtuais) => { console.log([...gastosAtuais, descricaoGasto]); return [...gastosAtuais, descricaoGasto]; }); }
- Os itens devem ser exibidos em uma lista contento
<Text>
para cada elemento - Utilizar a função
map
do javascript para percorrer os elementos da lista
{listaGastos.map((gasto) => <Text key={gasto}>{gasto}</Text>)}
- Permite distribuir os componentes visuais proporcionalmente na área de visualização
- A propriedade
flexDirecion
define como os componentes dentro da<View>
serão distribuídosrow
: alinhados lado a lado (linha)column
: alinhados um abaixo do outro (coluna) - Default
- Cada componente dentro da
<View>
tem uma propriedadeflex
para indicar o quanto de espaço irá ocupar - No exemplo baixo, 1 + 4 = 5, então Item 1 irá ocupar 1/5 do espaço horizontal
- Já o Item 2 irá ocupar 4/5
<View style={{marginTop: 50, flexDirection:'row'}}> <Text style={{flex: 1, borderColor: 'red', borderWidth: 1}}>Item 1</Text> <Text style={{flex: 4, borderColor: 'red', borderWidth: 1}}>Item 2</Text> </View>
- Delimita a região visível da aplicação e uma barra de status
<SafeAreaView style={{ flex: 1 }}>
<StatusBar />
</SafeAreaView>
- Para evitar que o teclado influencie no layout basta utilizar o componente
<KeyboardAvoidingView>
- Efetuar a refatoração dos elementos adicionando os estilos e aplicando o flex
return <View style={styles.container}> <TextInput style={styles.input} value={descricaoGasto} onChangeText={descricaoGastoHandler} placeholder="Descrição do Gasto"/> <Button style={styles.button} title="Adicionar" onPress={addDescricaoGastoHandler}/> <View style={styles.lista}> {listaGastos.map((gasto) => <Text key={gasto}>{gasto}</Text>)} </View> </View> }
- Estilos:
const styles = StyleSheet.create({ container: { flex: 1, // ocupa toda a dimensão vertical marginTop: '10%', borderWidth: 5, borderColor: 'red' }, input: { margin: 12, flex: 1, borderWidth: 1, padding: 10, }, button: { flex: 1 }, lista: { flex: 20, margin: 10 }
- Colocar o
<Button>
ao lado do<TextInput>
- Utilizar
<View>
para criar agrupamento dos componentes visuais do app<View style={{marginRight: 10, flexDirection: 'row', alignItems: 'center'}}> <TextInput style={styles.input} value={descricaoGasto} onChangeText={descricaoGastoHandler} placeholder="Descrição do Gasto"/> <Button style={styles.button} title="Adicionar" onPress={addDescricaoGastoHandler}/> </View>
- Importar
import { Image } from 'react-native';
- Permite incluir imagens
- Os arquivos de imagem podem ser armazenados dentro da pasta
assets
- Exemplo (copiar o arquivo
001-coin.png
para a pastaassets
):<Image source={require('../assets/001-coin.png')}/>
- Exibe uma lista de itens de forma otimizada:
data
: lista contendo os valores a serem exibidosrenderItem
: como os itens da lista serão exibidos visualmente - recebe como parâmetro um objeto JSON com os atributositem
(texto) eindex
(índice do item no array mapeado paradata
)keyExtractor
: chaves únicas para cada item da lista - recebe como parâmetro o índice do elemento no array indicado emdata
- Para organizar o código, criar uma função que retorna o item a ser exibido em cada linha da lista
const renderGasto = (item, index) => { return <Text style={styles.item}>{item}</Text>; }
- Referenciar a função
renderGasto
para exibir o item de gasto adicionado<FlatList data={listaGastos} renderItem={({item, index}) => renderGasto(item, index)} keyExtractor={idx => idx} />
- Definir o estilo para cada item:
item: { height: 40, marginLeft: 10, textAlignVertical: 'center' }
- Obtendo o item selecionado com
onPress
const removerGasto = idx => { let removerGasto = [...listaGastos]; removerGasto.splice(idx,1); addListaGastos(removerGasto); }; const renderGasto = (item, index) => { return <Text onPress={()=>removerGasto(index)} style={styles.item}>{item}</Text>; }
- A função
renderGasto
deve ter o nome trocado paraRenderGasto
- Os parâmetros devem ser encapsulados em um único parâmetro
props
- As propriedades são obtidas de
props
comoprops.index
eprops.item
const RenderGasto = (props) => { return <Text onPress={()=>removerGasto(props.index)} style={styles.item}>{props.item}</Text>; }
- Para acionar a função agora encapsulada em um componente:
<FlatList data={listaGastos} renderItem={({item, index}) => <RenderGasto index={index} item={item}/>} keyExtractor={idx => idx} />
- Incluindo uma
View
e estiloborderRadius
: arredondamento dos cantos do componente
itemgasto: {
margin: 8,
padding: 8,
borderRadius: 6,
backgroundColor: '#88ff'
},
- Criar uma
<View>
englobando<Text>
com o estiloitemgasto
aplicado
const RenderGasto = (props) => {
return <View style={styles.itemgasto}>
<Text onPress={()=>removerGasto(props.index)}
style={styles.item}>{props.item}</Text>
</View>;
}
- Uma outra forma de permitir que um componente seja pressionado pelo usuário é utilizando o componente
<Pressable>
ao invés de utilizar a propriedadeonPress
diretamente em um componente visual (como no caso do<Text>
)const RenderGasto = (props) => { return <Pressable onPress={()=>removerGasto(props.index)}> <View style={styles.itemgasto}> <Text style={styles.item}>{props.item}</Text> </View> </Pressable>; }
- Instalar o pacote
npm install --save react-native-draglist
- react-native-draglist - Importar o componente
import DragList, { DragListRenderItemInfo } from 'react-native-draglist';
- Criar uma função para formatar os itens da lista
const renderItem = (info) => { const { item, onDragStart, onDragEnd, isActive } = info; return ( <TouchableOpacity style={{ padding: 10, backgroundColor: "#CACA" }} key={item} onPressIn={onDragStart} onPressOut={onDragEnd}> <Text>{item.descricao}</Text> </TouchableOpacity> ); }
- Criar uma função para reorganizar os componentes da lista quando os itens forem reposicionados
const onReordered = async (fromIndex, toIndex) => { console.log(fromIndex, toIndex) const copy = [...gastos]; // Don't modify react data in-place const removed = copy.splice(fromIndex, 1); copy.splice(toIndex, 0, removed[0]); // Now insert at the new pos addGasto(copy); }
- Implementar o componente
DragList
<DragList data={gastos} keyExtractor={(item) => item.id} onReordered={onReordered} renderItem={renderItem} />
-
Criar uma pasta
components
dentro do projeto -
Criar um arquivo
RenderGasto.js
dentro da pasta -
Neste arquivo, importar os coponentes utilizados pelo
RenderGasto.js
import { StyleSheet, Text, View, Pressable } from 'react-native';
-
Mover a função
renderGasto
deApp.js
paraRenderGasto.js
export const RenderGasto = (props) => { return <Pressable onPress={() => console.log("Remover...")}> <View style={styles.itemgasto}> <Text style={styles.item}>{props.item} {props.index}</Text> </View> </Pressable>; }
-
Incluir também a folha de estilos:
const styles = StyleSheet.create({ itemgasto: { margin: 8, padding: 8, borderRadius: 6, backgroundColor: '#88ff' }, item: { height: 40, marginLeft: 10, textAlignVertical: 'center' } });
-
Em
App
referenciar o componente por meio deimport
import { RenderGasto } from './components/RenderGasto'
-
Problema: como acionar uma função que não está definida no componente
RenderGasto
para remover um item da lista (gasto)? -
Solução: passar uma função como parâmetro (callback) assim como
item
eindex
dentro deprops
e acioná-la dentro deonPress
const RenderGasto = (props) => { return <Pressable onPress={() => props.onRemoverGasto(props.index)}> <View style={styles.itemgasto}> <Text style={styles.item}>{props.item} {props.index}</Text> </View> </Pressable>; }
-
Então passar como parâmetro a função de callback
removerGasto
viaonRemoverGasto
<RenderGasto onRemoverGasto={removerGasto} index={index} item={item}/>
-
Transformar o
<TextInput>
e o<Button>
onde o gasto é adicionado na lista em um componente chamadoRenderEntradaGasto
; -
Criar um componente
TelaPrincipal
, transferir o conteúdo deApp
para ele e ajustar oApp
para conter apenas a referência ao novo componente criado:import TelaPrincipal from './components/TelaPrincipal'; export default function App() { return <TelaPrincipal /> }
-
Adicionar os campos Valor e Total no app de Controle de Gastos conforme abaixo:
-
-
No componente
RenderEntradaGasto
criado:- Alterar o layout para permitir a entrada do valor do gasto
- Criar uma nova variável de estado para armazenar o valor do gasto inserido (semelhante ao que foi feito para a descrição do gasto)
-
Adicionar o valor do gasto em cada gasto inserido na lista
-
Inserir uma imagem (ícone de moeda, por exemplo) na linha do gasto
<Image source={require('../assets/001-coin.png')}/>
-
Ao inserir ou remover um gasto, atualizar o total de despesas no campo Total (somente leitura)
-
EXTRA: utilizar
<Modal>
para exibir uma janela para avisar que o total de gastos ultrapassou R$ 1.000,00
- Exibe uma janela sobreposta (modal)
- Principais atributos:
visible
: indica quanto oModal
deve ser exibido (utilizar em conjunto com uma variável de estado true / false)transparent
: diz que oModal
deve ter seu fundo transparente (utlizar true)
<Modal visible={ exibirModal } transparent={true}> <View style={styles.centeredView}> <View style={styles.modalView}> <Text>Modal</Text> <Pressable style={[styles.button, styles.buttonClose]} onPress={() => setExibirModal(!exibirModal)}> <Text style={styles.textStyle}>Fechar</Text> </Pressable> </View> </View> </Modal> centeredView: { flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: 22, borderColor: 'red' }, modalView: { margin: 20, backgroundColor: 'white', borderRadius: 20, padding: 35, alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 5, }, button: { borderRadius: 20, padding: 10, elevation: 2, }, buttonClose: { backgroundColor: '#2196F3' }
-
React Navigation é um componente que implementa vários tipos de navegação para aplicações React Native
-
Instalação dos módulos necessários
npm install --save @react-navigation/native
npm install --save react-native-screens react-native-safe-area-context
-
Para implementar a navegação é necessário envolver os componentes em um
NavigationContainer
import { NavigationContainer } from '@react-navigation/native';
export default function App() { return (<NavigationContainer> <TelaPrincipal /> </NavigationContainer>) }
-
Um dos tipos de navegação mais comum é o Stack Navigator
npm install --save @react-navigation/native-stack
-
Criar o
NativeStackNavigator
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
-
Informar as telas que serão controladas pelo
Stack Navigator
return (<NavigationContainer> <Stack.Navigator> <Stack.Screen name="principal" component={TelaPrincipal}/> </Stack.Navigator> </NavigationContainer>)
-
Criar uma segunda tela
TelaDetalhes
que deve ser exibida ao clicar em um gasto na lista e incluí-la noStack
com o nomedetalhes
-
A
TelaPrincipal
agora receberá como parâmetroprops
um objetonavigator
(TelaPrincipal({ navigator })
) -
Para navegar para outra tela, basta utilizar
navigator.navigate('detalhes')
- Pode-se passar parâmetro também entre duas telas
navigator.navigate('detalhes', {idGasto: props.idx})
- Na tela destino, o acesso aos parâmetros deve ser feito por meio do componente
route
export default function TelaDetalhe({ route }) { const idGasto = route.params.idGasto; return <Text>{idGasto}</Text> }
-
É possível persistir dados localmente tanto em Android quando em iOS utilizando o banco de dados relacional SQLite
expo install expo-sqlite
-
Criar um arquivo
BancoDados.js
import * as SQLite from 'expo-sqlite'; const bcodados = SQLite.openDatabase('gastos.db');
import * as SQLite from 'expo-sqlite'; const bcodados = SQLite.openDatabase('gastos.db'); export const iniciar = () => { const retorno = new Promise((resolve, reject) => { }); return retorno; }
- Criando a estrutura do banco de dados
import * as SQLite from 'expo-sqlite'; const bcodados = SQLite.openDatabase('gastos.db'); export const iniciar = () => { const retorno = new Promise((resolve, reject) => { bcodados.transaction((tx) => { tx.executeSql('CREATE TABLE IF NOT EXISTS gastos (id INTEGER PRIMARY KEY NOT NULL, descricao TEXT NOT NULL, valor REAL NOT NULL)', [], () => { resolve(); }, (_, error) => reject(error)); }); }); return retorno; }
-
Em
App.js
iniciar().then(() => console.log("Iniciado banco de dados")).catch((err) => console.log(err));
-
Inserindo gastos
bcodados.transaction((tx) => {
tx.executeSql('INSERT INTO gastos (descricao, valor) VALUES (?, ?)',
[descricao, valor],
(_, result) => {
console.log(result)
resolve(result);
},
(_, error) => reject(error));
});
- Listando gastos
bcodados.transaction((tx) => {
tx.executeSql('SELECT * FROM gastos',
[],
(_, result) => {
resolve(result.rows._array);
},
(_, error) => reject(error));
});
- Existem várias bibliotecas para efetuar requisições HTTP
- O
axios
é uma delasnpm install --save axios
- Importar a biblioteca
import axios from 'axios';
- Efetuando POST
const ret = await axios.post('https://controle-gastos.glitch.me/', {descricao: descricaoGasto, valor: valorGasto})
- Efetuando GET
const ret = await axios.get("https://controle-gastos.glitch.me/") console.log(ret.data);
- Permitem enviar mensagens de notificação dentro dos padrões de cada platadorma móvel
- Com o uso das Notificações do Expo é possível trabalhar com notificações tanto para iOS quanto Android
npx expo install expo-notifications
-
Solicitanto autorização para receber notificações
const enviarNotificacao = async () => { const perm = await Notifications.getPermissionsAsync(); console.log(perm); if (perm.status === 'denied') { await Notifications.requestPermissionsAsync(); } }
-
Agendando uma notificação
Notifications.scheduleNotificationAsync({ content: { title: 'Controle de Gastos', body: "Você está gastando muito!", data: {valor: 1000} }, trigger: { seconds: 5 }, });
-
Respondendo a uma notificação (criar fora do escopo da função que declara o componente!!!)
Notifications.setNotificationHandler({ handleNotification: async () => { return { shouldShowAlert: true, shouldPlaySound: false, shouldSetBadge: false } }, });