E chegamos à quarta parte da minha série sobre como construir aplicativos móveis usando React Native. Para quem está chegando agora, não comece por esse post, mas ao invés disso, use os links abaixo para conhecer os outros capítulos da série.
E agora, continuando o que estávamos vendo, recém havíamos criado uma segunda tela, Form.js, em nosso app, mas ainda não tínhamos como navegar até ela.
Vamos mudar isso!
Navegando pelo App
Para fazer a navegação entre telas existem diversas formas e eu optei por lhe trazer uma que acredito ser simples, usando a lib React Navigation. Vamos instalar primeiro a lib principal:
1 2 3 |
npm install @react-navigation/native |
Esta lib instala uma série de dependências-chave para que tenhamos os comportamentos nativos de navegação/transição de telas. Uma vez com ela instalada, rode este comando para instalar mais algumas dependências necessárias pelo Expo.
1 2 3 |
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view |
Estas libs vão permitir que a gente crie recursos nativos de navegação. Mesmo que a gente não venha a utilizar todas estas bibliotecas diretamente, muitas delas são necessárias para a compilação correta do nosso app. A título de curiosidade, temos nesse comando:
- react-native-gesture-handler: para suportar gestos do usuário, como os de navegação;
- react-native-reanimated: animações de transição;
- react-native-screens: gestão nativa de telas;
- react-native-safe-area-context: gestão da área “segura” de uso, por causa daquelas telas esquisitas dos smartphones mais modernos (que não são mais retângulos simples);
- masked-view: permite máscaras de interface, para incluir views dentro;
E por fim, uma última lib para definir como a navegação será gerenciada pelo React Native.
1 2 3 |
npm install @react-navigation/stack |
Esta libs faz com que a navegação tenha o comportamento em pilha/stack, onde cada nova tela acessada é empilhada sobre a anterior, permitindo voltar na tela anterior com os recursos de cada plataforma, como o botão de Back/Voltar do Android. Outras opções de navegação incluiriam navegação por abas e por sidemenu, por exemplo, mas ficam para outra oportunidade.
Depois de instalar todas estas libs, não esqueça de reiniciar o Metro Bundler e scanear novamente o QRCode para atualizar completamente nosso Expo Client. É comum ele se perder depois da instalação de pacotes novos.
Para configurar nossa navegação, crie um arquivo Routes.js na raiz da sua aplicação, com o seguinte conteúdo.
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 |
import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import Home from './Pages/Home'; import Form from './Pages/Form'; const AppStack = createStackNavigator(); const Routes = () => { return ( <NavigationContainer> <AppStack.Navigator screenOptions={{ headerShown: false }}> <AppStack.Screen name="Home" component={Home} /> <AppStack.Screen name="Form" component={Form} /> </AppStack.Navigator> </NavigationContainer> ); } export default Routes; |
O que temos aqui em cima é, primeiramente, os imports necessários referente aos pacotes que instalamos anteriormente e depois referente a todas as telas que queremos configurar navegação no nosso app.
Logo abaixo, criamos um objeto de navegação em pilha (stack) e depois a estrutura de navegação em si, que apesar de parecer complexa, é bem simples e repetitiva. Dentro do NavigationContainer temos um AppStack.Navigator e dentro dele, uma linha para cada tela (AppStack.Screen) com o nome da tela (único) e componente a ser renderizado (os que importamos no início do arquivo).
Dica: dentro do AppStack.Navigator é possível determinar alguns estilos globais para a aplicação, como a statusbar, a cor de fundo das telas, etc através de uma propriedade chamada screenOptions. Além disso, note que usei uma opção headerShown também, para evitar que ele adicionasse uma barra superior automaticamente, sendo que já temos a nossa.
E por fim, para que nossas rotas passem a “existir” no nosso app, vamos voltar ao App.js, vamos importar esse nosso módulo de rotas e vamos chamá-lo no retorno da função App.
1 2 3 4 5 6 7 8 9 10 |
import React, {useState} from 'react'; import Routes from './Routes'; export default function App() { return ( <Routes /> ); } |
Se você olhar agora o seu smartphone, vai notar que nada mudou e que mesmo não referenciando mais a Home no nosso App.js, ela ainda está lá como tela default, isso pois é a primeira no nosso arquivo de rotas. Mas ainda fica a pergunta, como vamos fazer para trocar de uma tela para outra?
Vamos arrumar isso na nossa Home.js, iniciando com um novo import (useNavigation).
1 2 3 4 5 6 |
import React, {useState} from 'react'; import { StyleSheet, Text, View, TouchableOpacity } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import Header from './Header'; |
E agora vamos usar esta função dentro da function Home, como abaixo.
1 2 3 4 |
export default function Home() { const navigation = useNavigation(); |
Este objeto navigation possui uma function navigate que espera por parâmetro o name da tela que queremos (o mesmo name que definimos no arquivo Routes.js). Sendo assim, podemos chamar esta função em um segundo botão dentro da nossa Home.js:
1 2 3 4 5 |
<TouchableOpacity style={styles.button} onPress={() => navigation.navigate('Form')}> <Text>Navegar</Text> </TouchableOpacity> |
Agora sim, além do efeito óbvio de aparecer um segundo botão na tela, ao pressioná-lo com o dedo, você navegará para a tela Form.js, com a animação de transição nativa do dispositivo, bem como o comportamento nativo de voltar para a tela anterior arrastando o dedo (iOS) ou apertando o botão de voltar (Android).
Mas se isso não for o bastante e você quiser colocar um botão de voltar na tela Form.js, você importar o mesmo useNavigation e chamar navigation.goBack() ao pressionar seu botão de voltar. Não vou entregar pronto aqui que é pra você exercitar!
Lidando com imagens
Agora que temos esta página de cadastro, vamos criar um formulário nela, bem simples, apenas para exercitar alguns conceitos. Vou aproveitar esta oportunidade para mostrar alguns truques novos, a começar pelo uso de imagens em nosso app, algo importantíssimo.
Crie uma nova pasta chamada Assets dentro de Pages. Sim, eu sei que na raiz da aplicação tem outra pasta chamada assets, mas ignore ela, tem outra função. Todos os recursos (assets) estáticos da nossa aplicação, como imagens, ficarão aqui dentro, a começar por uma imagem de cadastro de usuário que deixaremos no topo da tela e aqui vai a primeira dica sobre imagens: sempre tenha 3 tamanhos para a mesma imagem, como no caso abaixo que peguei do site findicons.com.
Coloque os 3 tamanhos na pasta Pages/Assets, sendo o menor tamanho com o nome original, o segundo tamanho com o mesmo nome mas terminado em @2x e o terceiro tamanho (maior de todos) com o mesmo nome mas terminado em @3x, como abaixo.
- user_group_new.png
- [email protected]
- [email protected]
Usando este padrão, poderemos referenciar somente a primeira deles na nossa aplicação e o React Native automaticamente selecionará a imagem certa conforme a resolução e densidade de pixels da tela do dispositivo que estiver rodando o nosso app.
Mas como vamos referenciar essas imagens na nossa tela?
Vamos voltar ao Form.js e vamos substituir o código antigo por este:
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 |
import React from 'react'; import { StyleSheet, Text, View, Image } from 'react-native'; import Header from './Header'; import logo from './Assets/user_group_new.png'; function Form() { return ( <> <Header title="Cadastro" /> <View style={styles.container}> <Image source={logo} style={styles.topImage} /> <Text style={styles.title}>Preencha o formulário abaixo:</Text> </View> </> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', }, topImage: { margin: 20, }, title: { fontSize: 20, } }); export default Form; |
O que há de novo aqui? Primeiro, dois imports: o componente Image (do React Native) e a imagem, que é importada do mesmo jeito de um componente comum.
Depois, dentro da function Form, eu incluí um componente Image com o source (fonte) apontando para a imagem que importamos e definindo alguns estilos referenciados mais abaixo, estilos esses que alteram o texto da tela também.
O resultado você confere abaixo ou no seu próprio smartphone ou simulador.
Além desse uso mais direto da imagem, podíamos ter imagens em segundo plano (background) e até mesmo imagens como botões. Mas esses exemplos ficam para um segundo momento.
Criando o formulário
Agora que criamos o topo do nosso formulário, está na hora de criar o formulário em si. Vai ser um formulário simples, mas ao mesmo tempo bem didático: dois campos de texto e uma lista de seleção. Para que a gente possa usar uma lista de seleção, componente que não existe nativo no React Native, instale mais uma dependência no seu projeto, a react-native-picker-select:
1 2 3 |
npm install react-native-picker-select |
Além da lista de seleção, os demais componentes que vamos usar são nativos, então basta importá-los corretamente, como abaixo.
1 2 3 4 5 6 7 |
import React from 'react'; import { StyleSheet, Text, View, Image, TextInput, TouchableOpacity } from 'react-native'; import Picker from 'react-native-picker-select'; import Header from './Header'; import logo from './Assets/user_group_new.png'; |
Vamos começar de maneira simples, definindo dois inputs de texto comuns e um botão, mas todos estilizados.
1 2 3 4 5 6 7 8 9 |
<View style={styles.inputContainer}> <TextInput style={styles.input} placeholder="Digite o nome" /> <TextInput style={styles.input} placeholder="Digite a idade" keyboardType={'numeric'} /> <TouchableOpacity style={styles.button}> <Text style={styles.buttonText}>Salvar</Text> </TouchableOpacity> </View> |
O TextInput é um campo de texto simples, sendo que a propriedade placeholder é o texto padrão que aparece no seu interior até que o usuário digite algo. Apenas ajustei o campo da idade para ser numérico, usando a propriedade keyboardType mas poderíamos ter adicionado muitas outras propriedades como maxLength (número máximo de caracteres no campo), autoCorrect (corretor ortográfico do smartphone), autoCapitalize (para usar apenas maiúsculas), entre outras.
O TouchableOpacity é um botão que já usamos anteriormente. Os estilos referenciados estão abaixo.
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 |
const styles = StyleSheet.create({ container: { alignItems: 'center', }, inputContainer: { margin: 20, alignItems: 'stretch', }, topImage: { margin: 20, }, title: { fontSize: 20, }, input: { marginTop: 10, height: 60, backgroundColor: '#fff', borderRadius: 10, paddingHorizontal: 24, fontSize: 16, alignItems: 'stretch', }, button: { marginTop: 10, height: 60, backgroundColor: 'green', borderRadius: 10, paddingHorizontal: 24, fontSize: 16, alignItems: 'center', justifyContent: 'center', }, buttonText: { color: '#fff', fontWeight: 'bold', } }); |
O que temos de novidade aí?
- alignItems: ‘stretch’, faz com que o componente ocupe toda largura da tela;
- borderRadius: define um ângulo de arredondamento da borda;
- paddingHorizontal: margem interna nas extremidades direita e esquerda;
O resultado, temos abaixo.
Pera, mas tem um componente diferente ali…que “RS” é aquele no final do screenshot do formulário?
Esse componente de lista é um pouco mais complexo que os demais, a começar pois ele depende de um array de valores que vamos definir fora do mesmo, mas dentro da function Form. Também aproveito para declarar o placeholder que será utilizado por ele.
1 2 3 4 5 6 7 8 9 10 |
function Form() { const ufs = [ { label: 'RS', value: '1' }, { label: 'SC', value: '2' }, { label: 'PR', value: '3' },//coloque os outros estados aqui... ]; const placeholder = { label:'Selecione o estado', value: null, color: 'black'}; |
E também, ele depende de uma folha de estilos específica para ele, contendo um objeto inputIOS e um inputAndroid, como abaixo. Esses nomes não são opcionais, são esperados pelo componente para estilizá-lo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const pickerSelectStyles = StyleSheet.create({ inputIOS: { marginTop: 10, height: 60, backgroundColor: '#fff', borderRadius: 10, paddingHorizontal: 24, fontSize: 16, alignItems: 'stretch', }, inputAndroid: { marginTop: 10, height: 60, backgroundColor: '#fff', borderRadius: 10, paddingHorizontal: 24, fontSize: 16, alignItems: 'stretch', }, }); |
Agora que criamos todos os recursos necessários para nosso picker, vamos adicioná-los na tela desta forma.
1 2 3 |
<Picker placeholder={placeholder} onValueChange={() => {}} style={pickerSelectStyles} items={ufs} /> |
Atenção ao placeholder, que é o objeto padrão do componente (declaramos anteriormente, lembra?), ao onValueChange que é a function que vai ser disparada toda vez que for selecionada uma opção (nada por enquanto), a propriedade items que recebe nosso array de estados e a propriedade style que recebe a nossa stylesheet específica para o componente.
O comportamento desse componente você confere na imagem abaixo (no caso do iOS, no Android a aparência é diferente).
Dica: caso o formulário de alguma aplicação sua fique muito grande e parte dele fique fora da área de visão da tela, use uma ScrollView ao invés da View tradicional para a tela se tornar “rolável”. Outra dica é se o teclado ficar acima dos campos quando for digitar, você pode usar o KeyboardAvoidingView por fora dos campos, como um contâiner.
Eu coloquei uma lista fixa de UFs, o que é ok para um campo como esse, mas já pensou se os valores do seu campo tivessem de ser dinamicamente carregados através de um banco de dados fora do app?
E o nosso botão de Salvar, onde que ele vai salvar?
Essas e outras perguntas serão respondidas no próximo capítulo desta nossa saga de aprendizado que você confere clicando neste link!
Gostou do tutorial de hoje? Conheça meu curso online de Node.js clicando no banner abaixo.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.