Recursos nativos com React Native (parte 1): navegação e a tela de novo lugar

Comece o app de lugares favoritos com React Native e Expo, configurando a navegação em pilha do React Navigation, botões no cabeçalho com ícones e a tela de cadastro de um novo lugar.

1. Visão geral

Neste material desenvolveremos uma aplicação que ilustra o uso de diversos recursos nativos, como a câmera, armazenamento local, GPS, além do uso de mapas. Nesta primeira parte, preparamos a estrutura da aplicação: as telas, a navegação entre elas e a tela de cadastro de um novo lugar.

O que você vai aprender

  • Organizar a aplicação em telas, componentes e constantes
  • Configurar a navegação em pilha com @react-navigation/native e @react-navigation/native-stack
  • Personalizar o cabeçalho das telas (cores e botões)
  • Criar um botão de cabeçalho reutilizável com react-navigation-header-buttons e @expo/vector-icons
  • Montar a tela de novo lugar com ScrollView, TextInput e estado

O que você vai precisar

  • Node.js e o Expo CLI (comando expo)
  • VS Code
  • Um celular com o Expo Go ou um emulador

2. Criando o projeto e ajustes iniciais

Criando o projeto

Como sempre, o primeiro passo é criar um projeto em seu workspace. Depois, abra uma instância do VS Code vinculada ao diretório do projeto. Ao criar o projeto, escolha o template Blank. Coloque também o projeto em execução. Use os comandos a seguir.

Terminal

expo init nome_do_projeto
cd nome_do_projeto
code .
npm start

Ajustes iniciais

Começamos com ajustes iniciais na aplicação. O conteúdo inicial de App.js é dado na Listagem 2.2.1.

App.js · Listagem 2.2.1

import React from 'react';
import { View } from 'react-native';

export default function App() {
  return (
    <View>
    </View>
  );
}

Também criamos uma pasta para abrigar as cores que utilizaremos. Ela fica na raiz da aplicação e se chama constantes. Crie também um arquivo chamado Cores.js dentro dela e já adicione uma cor associada à chave primary (é só um nome, pode ser qualquer outro), como mostra a Listagem 2.2.2.

constantes/Cores.js · Listagem 2.2.2

export default {
  primary: '#FC9208'
}

A seguir, na raiz do projeto, crie uma pasta chamada telas e os seguintes arquivos dentro dela. Cada um conterá a definição de uma tela da aplicação.

MapaTela.js
NovoLugarTela.js
DetalhesDoLugarTela.js
ListaDeLugaresTela.js

3. Navegação entre telas

Utilizaremos pacotes específicos para a navegação entre telas. Eles podem ser instalados da seguinte forma.

Comece instalando o pacote principal do React Native, próprio para navegação.

Terminal

npm install @react-navigation/native

A seguir, instale as seguintes dependências. Certifique-se de utilizar o expo para fazer a instalação. Ele se encarrega de fazer a instalação de pacotes de versões compatíveis.

Terminal

expo install react-native-screens react-native-safe-area-context

A navegação entre telas pode envolver uma pilha, semelhante àquela utilizada pelos navegadores em que as páginas visitadas são empilhadas e desempilhadas conforme o usuário navega e clica no botão voltar. Instale o pacote a seguir. Ele nos fornece a implementação de uma pilha assim.

Terminal

npm install @react-navigation/native-stack

Agora, vamos criar um arquivo em que a navegação de telas vai ser especificada. Para isso, crie uma pasta chamada navegacao e, dentro dela, crie um arquivo chamado LugaresNavigator.js. Seu conteúdo aparece na Listagem 2.3.1.

navegacao/LugaresNavigator.js · Listagem 2.3.1

import React from 'react'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import DetalhesDoLugarTela from '../telas/DetalhesDoLugarTela';
import ListaDeLugaresTela from '../telas/ListaDeLugaresTela';
import MapaTela from '../telas/MapaTela';
import NovoLugarTela from '../telas/NovoLugarTela';

const Stack = createNativeStackNavigator()

const container = (
  <NavigationContainer>
    <Stack.Navigator initialRouteName="ListaDeLugares">
      <Stack.Screen name="DetalhesDoLugar" component={DetalhesDoLugarTela}/>
      <Stack.Screen name="ListaDeLugares" component={ListaDeLugaresTela}/>
      <Stack.Screen name="Mapa" component={MapaTela}/>
      <Stack.Screen name="NovoLugar" component={NovoLugarTela}/>
    </Stack.Navigator>
  </NavigationContainer>
)

export default container

Podemos configurar algumas opções do cabeçalho. Na Listagem 2.3.3 estamos definindo cor para o cabeçalho e a cor tint, que será usada pelo título e outras partes no cabeçalho.

navegacao/LugaresNavigator.js · Listagem 2.3.3

import React from 'react'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import DetalhesDoLugarTela from '../telas/DetalhesDoLugarTela';
import ListaDeLugaresTela from '../telas/ListaDeLugaresTela';
import MapaTela from '../telas/MapaTela';
import NovoLugarTela from '../telas/NovoLugarTela';
import Cores from '../constantes/Cores'

const Stack = createNativeStackNavigator()

const container = (
  <NavigationContainer>
    <Stack.Navigator
      initialRouteName="ListaDeLugares"
      screenOptions={{
        headerStyle: {backgroundColor: Cores.primary},
        headerTintColor: 'white'
      }}>
      <Stack.Screen name="DetalhesDoLugar" component={DetalhesDoLugarTela}/>
      <Stack.Screen name="ListaDeLugares" component={ListaDeLugaresTela}/>
      <Stack.Screen name="Mapa" component={MapaTela}/>
      <Stack.Screen name="NovoLugar" component={NovoLugarTela}/>
    </Stack.Navigator>
  </NavigationContainer>
)

export default container

A forma como esse componente pode ser utilizado é exibida na Listagem 2.3.4. Nosso componente principal irá retorná-lo. Estamos no arquivo App.js agora.

App.js · Listagem 2.3.4

import React from 'react';
import container from './navegacao/LugaresNavigator'

export default function App() {
  return container;
}

4. Conteúdo inicial das telas

Vamos definir o conteúdo inicial de cada uma das telas. Veja as listagens de 2.4.1 a 2.4.4. Evidentemente, cada tela será definida no seu próprio arquivo, aqueles que criamos anteriormente na pasta telas.

telas/DetalhesDoLugarTela.js · Listagem 2.4.1

import React from 'react';
import { View, StyleSheet, Text } from 'react-native';

const DetalhesDoLugarTela = (props) => {
  return (
    <View>
      <Text>Detalhes do lugar Tela</Text>
    </View>
  )
};

const estilos = StyleSheet.create({
});

export default DetalhesDoLugarTela;

telas/ListaDeLugaresTela.js · Listagem 2.4.2

import React from 'react';
import { View, StyleSheet, Text } from 'react-native';

const ListaDeLugaresTela = (props) => {
  return (
    <View>
      <Text>Lista de Lugares</Text>
    </View>
  )
};

const estilos = StyleSheet.create({
});

export default ListaDeLugaresTela;

telas/MapaTela.js · Listagem 2.4.3

import React from 'react';
import { View, StyleSheet, Text } from 'react-native';

const MapaTela = (props) => {
  return (
    <View>
      <Text>MapaTela</Text>
    </View>
  )
};

const estilos = StyleSheet.create({
});

export default MapaTela;

telas/NovoLugarTela.js · Listagem 2.4.4

import React from 'react';
import { View, StyleSheet, Text } from 'react-native';

const NovoLugarTela = (props) => {
  return (
    <View>
      <Text>Novo Lugar Tela</Text>
    </View>
  )
};

const estilos = StyleSheet.create({
});

export default NovoLugarTela;

5. Botão no cabeçalho da lista de lugares

Podemos alterar algumas características da barra de título de cada tela, trocando o título, exibindo botões etc.

Vamos trocar o título e adicionar um botão que permite ir até a tela de adição de lugares. Para adicionar o botão, precisamos instalar dois novos pacotes, veja:

Terminal

npm install --save react-navigation-header-buttons
npm install --save @expo/vector-icons

O botão será um componente reutilizável entre as diversas telas. Por isso, vamos criar um componente específico para ele. Comece criando uma pasta chamada componentes. A seguir, crie um arquivo chamado BotaoCabecalho.js. O seu conteúdo é dado na Listagem 2.5.1.

componentes/BotaoCabecalho.js · Listagem 2.5.1

import React from 'react'
import { HeaderButton } from 'react-navigation-header-buttons'
import { Ionicons } from '@expo/vector-icons';

const BotaoCabecalho = (props) => {
  return (
    <HeaderButton
      {...props}
      IconComponent={Ionicons}
      iconSize={23}
      color={'black'}
    />
  );
};

export default BotaoCabecalho;

A seguir, como mostra a Listagem 2.5.2, aplicamos o novo botão. Estamos no arquivo LugaresNavigator.js.

navegacao/LugaresNavigator.js · Listagem 2.5.2

import React from 'react'
import { Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import DetalhesDoLugarTela from '../telas/DetalhesDoLugarTela';
import ListaDeLugaresTela from '../telas/ListaDeLugaresTela';
import MapaTela from '../telas/MapaTela';
import NovoLugarTela from '../telas/NovoLugarTela';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import Cores from '../constantes/Cores'
import BotaoCabecalho from '../componentes/BotaoCabecalho';

const Stack = createNativeStackNavigator()

const container = (
  <NavigationContainer>
    <Stack.Navigator
      initialRouteName="ListaDeLugares"
      screenOptions={{
        headerStyle: {backgroundColor: Cores.primary},
        headerTintColor: 'white'
      }}>
      <Stack.Screen name="DetalhesDoLugar" component={DetalhesDoLugarTela}/>
      <Stack.Screen
        name="ListaDeLugares"
        component={ListaDeLugaresTela}
        options={(props) => ({
          headerRight: () => <HeaderButtons
            HeaderButtonComponent={BotaoCabecalho}>
            <Item
              title="Adicionar"
              iconName='md-add'
              onPress={() =>{
                console.log ("Chamou")
                props.navigation.navigate('NovoLugar')
              }}
            />
          </HeaderButtons>
        })}
      />
      <Stack.Screen name="Mapa" component={MapaTela}/>
      <Stack.Screen name="NovoLugar" component={NovoLugarTela}/>
    </Stack.Navigator>
  </NavigationContainer>
)

export default container

6. A tela NovoLugarTela

A tela NovoLugarTela permitirá que o usuário armazene novos lugares. Ela possui uma ScrollView que engloba os campos necessários para que o usuário possa fazer a inserção do nome do novo lugar e do botão que, quando clicado, irá armazenar o lugar. Veja a Listagem 2.6.1. Estamos no arquivo NovoLugarTela.js.

telas/NovoLugarTela.js · Listagem 2.6.1

import React from 'react';
import Cores from '../constantes/Cores'
import {Button, View, ScrollView, StyleSheet, Text, TextInput } from 'react-native';

const NovoLugarTela = (props) => {
  return (
    <ScrollView>
      <View>
        <Text>Novo lugar</Text>
        <TextInput />
        <Button
          title="Salvar Lugar"
          color={Cores.primary}
          onPress={() => { }}
        />
      </View>
    </ScrollView>
  )
};

export default NovoLugarTela;

A Listagem 2.6.2 define alguns objetos de estilo a serem aplicados nos componentes da tela NovoLugarTela.

telas/NovoLugarTela.js · Listagem 2.6.2

import React from 'react';
import Cores from '../constantes/Cores'
import {Button, View, ScrollView, StyleSheet, Text, TextInput } from 'react-native';

const NovoLugarTela = (props) => {
  return (
    <ScrollView>
      <View style={estilos.form}>
        <Text style={estilos.titulo}>Novo lugar</Text>
        <TextInput style={estilos.textInput}/>
        <Button
          title="Salvar Lugar"
          color={Cores.primary}
          onPress={() => { }}
        />
      </View>
    </ScrollView>
  )
};

const estilos = StyleSheet.create({
  form: {
    margin: 30
  },
  titulo: {
    fontSize: 18,
    marginBottom: 15
  },
  textInput: {
    borderBottomColor: '#DDD',
    borderBottomWidth: 2,
    marginBottom: 15,
    paddingVertical: 4
  }});

export default NovoLugarTela;

Vamos adicionar uma variável ao estado do componente para armazenar o novo lugar digitado, e vinculá-la ao campo de entrada, como na Listagem 2.6.3.

telas/NovoLugarTela.js · Listagem 2.6.3

import React, { useState } from 'react';
import Cores from '../constantes/Cores'
import {Button, View, ScrollView, StyleSheet, Text, TextInput } from 'react-native';

const NovoLugarTela = (props) => {
  const [novoLugar, setNovoLugar] = useState('');
  const novoLugarAlterado = (texto) => {
    setNovoLugar(texto);
  }
  return (
    <ScrollView>
      <View style={estilos.form}>
        <Text style={estilos.titulo}>Novo lugar</Text>
        <TextInput
          style={estilos.textInput}
          onChangeText={novoLugarAlterado}
          value={novoLugar}
        />
        <Button
          title="Salvar Lugar"
          color={Cores.primary}
          onPress={() => { }}
        />
      </View>
    </ScrollView>
  )
};

const estilos = StyleSheet.create({
  form: {
    margin: 30
  },
  titulo: {
    fontSize: 18,
    marginBottom: 15
  },
  textInput: {
    borderBottomColor: '#DDD',
    borderBottomWidth: 2,
    marginBottom: 15,
    paddingVertical: 4
  }});

export default NovoLugarTela;

A seguir, como na Listagem 2.6.4, definimos uma função e a vinculamos ao evento onPress do botão. Ela será implementada em breve.

telas/NovoLugarTela.js · Listagem 2.6.4

import React, { useState } from 'react';
import Cores from '../constantes/Cores'
import {Button, View, ScrollView, StyleSheet, Text, TextInput } from 'react-native';

const NovoLugarTela = (props) => {
  const [novoLugar, setNovoLugar] = useState('');
  const novoLugarAlterado = (texto) => {
    setNovoLugar(texto);
  }
  const adicionarLugar = () => {
    console.log ("Adicionando...")
  }
  return (
    <ScrollView>
      <View style={estilos.form}>
        <Text style={estilos.titulo}>Novo lugar</Text>
        <TextInput
          style={estilos.textInput}
          onChangeText={novoLugarAlterado}
          value={novoLugar}
        />
        <Button
          title="Salvar Lugar"
          color={Cores.primary}
          onPress={adicionarLugar}
        />
      </View>
    </ScrollView>
  )
};

const estilos = StyleSheet.create({
  form: {
    margin: 30
  },
  titulo: {
    fontSize: 18,
    marginBottom: 15
  },
  textInput: {
    borderBottomColor: '#DDD',
    borderBottomWidth: 2,
    marginBottom: 15,
    paddingVertical: 4
  }});

export default NovoLugarTela;

7. Exercícios

  1. Utilize a sua aplicação de cadastro de contatos para esse exercício.

    1.1 Permita que o usuário cadastre novos contatos utilizando uma nova tela. Ela deve ser acionada por meio do mecanismo de navegação visto em aula.

Próximos passos

  • A aplicação continua no codelab rn-recursos-nativos-parte-2 (Redux e câmera).

Referências

Todos os codelabs