Iniciando com React Native: Navegação e Autenticação com JWT

Esse post é a quarta parte da série de posts “Clone AirBnB com AdonisJS, React Native e ReactJS” onde iremos construir do zero uma aplicação web com ReactJS e também uma aplicação mobile com React Native com dados servidos através de uma API REST feita com NodeJS utilizando o framework AdonisJS.

Chegamos finalmente na primeira parte de React Native dessa série onde já passamos pelo AdonisJS e ainda vamos passar pelo ReactJS. Nesse post vamos começar a criação do projeto para consumo da API REST criada nos posts anteriores.

Se você não seguiu as três primeiras partes do post, fica tranquilo, o código construído até aqui está no Github, só clicar aqui.

Obs.: Não se esqueça de rodar a API do Adonis, pois do contrário o app não conseguirá estabelecer conexão.

Iniciando o Projeto

Para começarmos o desenvolvimento da nossa aplicação, vamos instalar o CLI (Command Line Interface) do React Native que vai nos ajudar muito durante o desenvolvimento:

yarn global add react-native-cli
// npm i -g react-native-cli

Em uma pasta de sua escolha execute agora o seguinte comando que irá criar um novo projeto com a estrutura de uma aplicação React Native:

react-native init AirBnbApp

O AirBnbApp é o nome que dei ao meu projeto, mas você pode dar o nome que preferir.

Para testar se o projeto funcionou e até para configurar seu ambiente de desenvolvimento recomendo fortemente que dê uma olhada nesse link.

Configurando o Projeto

Nessa etapa vamos realizar a instalação e as configurações necessárias para que o app consiga consumir a API REST e fazer também o uso de Navegação.

Iremos instalar algumas dependências para adicionar funcionalidades no nosso app, para instalar todas de uma vez você pode executar o comando:

yarn add axios prop-types react-navigation styled-components
// npm install axios prop-types react-navigation styled-components

Quando esse processo acabar você terá um projeto pré configurado com todas as dependências configuradas, vou dar uma explicação da função de cada uma:

  • Axios: Cliente HTTP usado para enviar requisições à API;
  • Prop Types: Lib para checagem de tipo das props de componentes React;
  • React Navigation:Lib implementação de navegação na aplicação;
  • Styled Components: Lib usada estilização dos componentes.

Criando a estrutura do projeto

Para deixar o projeto mais organizado e seguindo boas práticas vamos criar uma estrutura de arquivo onde estejam separados os arquivos das telas da aplicação, o arquivo de configuração das rotas, o entry point da aplicação e as imagens, a estrutura ficará algo como:

--src
----images
------airbnb_logo.png
----pages
------main
--------index.js
--------styles.js
------signIn
--------index.js
--------styles.js
------signUp
--------index.js
--------styles.js
----services
------api.js
----index.js
----routes.js

E para que funcione corretamente você deve também ajustar a importação do componente App no arquivo index.js da raiz do projeto, basta mudar ./App para ./src .

Obs.: A imagem do logo será disponibilizada no repositório juntamente com os outros arquivos.


Antes de começarmos com o código em React Native, caso você ainda não conheça o framework, ou quer reforçar seus conhecimentos em Javascripte ES6/ES7/ ES8, dê uma olhada nos nossos minicursos gratuitos.


Criando o Entry Point da pasta src

Como deletamos o arquivo App.js da raiz do projeto precisamos de outro arquivo para ser chamado na inicialização da aplicação, e para isso vamos usar o arquivo index.js da pasta src, o código dele é bem simples vamos apenas importar as rotas e usá-las no componente App o código desse arquivo vai ficar assim:

import React from 'react';

import Routes from './routes';

const App = () => <Routes />;

export default App;

Criando a configuração da navegação

Com o Entry Point devidamente configurado vamos agora configurar o arquivo que será o responsável pela nevegação entre as telas, esse arquivo é o routes.js, e seu código ficou assim:

import { createStackNavigator } from 'react-navigation';

import SignIn from './pages/signIn';
import SignUp from './pages/signUp';
import Main from './pages/main';

const Routes = createStackNavigator({
  SignIn,
  SignUp,
  Main,
});

export default Routes;

Caso esteja com um pouco de dúvida ou inseguro com a configuração de navegação, corre no blog e leia esse post do Higo falando sobre o React Navigation na versão 2 😉


Em resumo, o arquivo de rotas apenas importa as telas das respectivas pastas e em seguida as insere em uma Stack de navegação.

Criando a configuração do Axios (HTTP Client)

O Axios é a lib que vamos usar para enviar as requisições para a API desenvolvida nos posts anteriores, para configurar essa lib é bem simples, basta inserir o seguinte trecho de código no arquivo api.js:

import axios from 'axios';

/* Endereços para cada emulador/simulador:
** Genymotion:              http://10.0.3.2:3333/
** Emulador Android Studio: http://10.0.2.2:3333/
** Simulador IOS:           http://localhost:3333/
*/
const api = axios.create({
  baseURL: 'http://localhost:3333/',
});

export default api;

E pronto, a aplicação já está pronta para enviar requisições \o/

Obs.: Dependendo de onde você for testar a aplicação terá que mudar o endereço do baseURL, nos comentários há os endereços de algumas das opções que você possivelmente estará usando.

Assim finalizamos as configurações da aplicação(finalmente\o/) , faltando apenas a criação da interface das telas de Login, Cadastro e Principal (Mapa), caso queria conferir como ficou o código, acesse esse link.

Criando as telas da aplicação

Para estilizar as telas iremos utilizar a lib Styled Components que permite que a estilização seja feita de uma maneira um pouco diferente, com ela podemos inserir o CSS no JS, mas o ponto chave do Styled Components é o React, ele segue a mesma linha do React, a componentização, ou seja, ele permite a transformação do CSS em componentes, e o estilo gerado fica apenas no escopo do componente, não afetando os demais componentes, por isso se torna uma alternativa super interessante.

Para saber mais sobre essa lib você pode dar uma olhada nesse link da documentação 😉

Tela de Login

A primeira telas que vamos criar será a de Login, sua interface ficará como abaixo:

E nesse post vou fazer diferente, sempre começamos pela estrutura do arquivo index.js para depois estilizar, mas devido à sintaxe peculiar do Styled Components vou começar com o arquivo styles.js para você ver como serão feitas as estilizações dos componentes, não irei postar o código completo, apenas de alguns componentes para dar uma rápida explicação.

const Container = styled.View`
  flex: 1;
  alignItems: center;
  justifyContent: center;
  backgroundColor: #F5F5F5;
`;

const Logo = styled.Image`
  height: 30%;
  marginBottom: 40px;
`;

const Input = styled.TextInput`
  paddingHorizontal: 20px;
  paddingVertical: 15px;
  borderRadius: 5px;
  backgroundColor: #FFF;
  alignSelf: stretch;
  marginBottom: 15px;
  marginHorizontal: 20px;
  fontSize: 16px;
`;

Como você deve ter percebido não usamos mais o StyleSheet.create()passando um objeto como parâmetro, com o Styled Components criamos uma constante para cada componente com o prefixo styled.<nome_do_componente>, em seguida dentro de um bloco de crases (`) passamos os estilos igual CSS, sem o uso de apóstrofe (‘) nem vírgula(,) no final das linhas, portanto seguindo esse padrão de nomeação você pode criar qualquer componente como se estivesse usando o StyleSheet.

Você pode encontrar o código final do arquivo de estilos nesse link, depois basta copiar o conteúdo do link e colar no arquivo src/pages/signIn/styles.js.

Agora sim podemos criar a estrutura do arquivo index.jsque irá importar todos os componentes criados no arquivo de estilo, a estrutura do componente SignIn ficará assim:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { StatusBar, AsyncStorage } from 'react-native';
import { StackActions, NavigationActions } from 'react-navigation';

import api from '../../services/api';

import {
  Container,
  Logo,
  Input,
  ErrorMessage,
  Button,
  ButtonText,
  SignUpLink,
  SignUpLinkText,
} from './styles';

export default class SignIn extends Component {
  render() {
    return (
      <Container>
        <StatusBar hidden />
        <Logo source={require('../../images/airbnb_logo.png')} resizeMode="contain" />
        <Input
          placeholder="Endereço de e-mail"
          value={this.state.email}
          onChangeText={this.handleEmailChange}
          autoCapitalize="none"
          autoCorrect={false}
        />
        <Input
          placeholder="Senha"
          value={this.state.password}
          onChangeText={this.handlePasswordChange}
          autoCapitalize="none"
          autoCorrect={false}
          secureTextEntry
        />
        {this.state.error.length !== 0 && <ErrorMessage>{this.state.error}</ErrorMessage>}
        <Button onPress={this.handleSignInPress}>
          <ButtonText>Entrar</ButtonText>
        </Button>
        <SignUpLink onPress={this.handleCreateAccountPress}>
          <SignUpLinkText>Criar conta grátis</SignUpLinkText>
        </SignUpLink>
      </Container>
    );
  }
}

Primeiramente temos o bloco de importação de alguns componentes, dentre eles está o arquivo com a configuração do Axios, os estilos criados no styles.js, algumas funções do React Navigation que iremos usar um pouco mais a frente e o Prop Types para validarmos as props recebidas do React navigation.

Na sequência temos o método render, que está apenas fazendo o uso dos componentes criados, nele temos um Container que é uma View, a StatusBar que vamos ocultar em algumas telas, o Logo da Airbnb, os 2 Inputs, um trecho de código JS verificando se houve algum erro do retorno da API (veremos na sequência como funciona a exibição dos erros), e por último os 2 Botões, o de Login e o de Cadastro.

Você deve ter reparado que nos componentes de Input e nos botões temos algumas funções e até o uso do state, e é o que vamos criar agora.

Primeiro vamos ocultar o Header que vem por padrão nas telas da Stack do React Navigation, para isso basta inserir o seguinte trecho de código logo no começo da classe do componente:

static navigationOptions = {
  header: null,
};

Com o Header oculto vamos criar o state, você pode inseri-lo logo abaixo da declaração do navigationOptions, nele teremos uma variável para guardar o email digitado, outra variável para a senha e uma para guardar as mensagens de erro, ficando assim:

state = { email: '', password: '', error: '' };

Agora vamos à criação das funções, a primeira que vamos criar é para tratar quando o usuário estiver digitando algo no campo de email, a handleEmailChange, ela apenas irá recuperar um parâmetro e guardá-lo na variável email do state, a segunda é igual à primeira, porém com o nome handlePasswordChange e ao invés de guardar um valor no o email ela irá guardar no password, e por último é a função handleCreateAccountPress que irá navegar para a tela de cadastro, ela irá usar o método navigate que está disponível nas props do componente graças ao React Navigation, o código delas ficou assim:

handleEmailChange = (email) => {
  this.setState({ email });
};

handlePasswordChange = (password) => {
  this.setState({ password });
};

handleCreateAccountPress = () => {
  this.props.navigation.navigate('SignUp');
};

Agora falta criarmos a função handleSignInPress que é a responsável pela comunicação com a API e dar um resultado de sucesso ou erro para o Login, irei primeiro postar o código e logo abaixo explico o fluxo dela.

handleSignInPress = async () => {
    if (this.state.email.length === 0 || this.state.password.length === 0) {
      this.setState({ error: 'Preencha usuário e senha para continuar!' }, () => false);
    } else {
      try {
        const response = await api.post('/sessions', {
          email: this.state.email,
          password: this.state.password,
        });
          
        await AsyncStorage.setItem('@AirBnbApp:token', response.data.token);

        const resetAction = StackActions.reset({
          index: 0,
          actions: [
            NavigationActions.navigate({ routeName: 'Main' }),
          ],
        });
        this.props.navigation.dispatch(resetAction);
      } catch (_err) {
        this.setState({ error: 'Houve um problema com o login, verifique suas credenciais!' });
      }
    }
  };

Logo na declaração dela podemos ver que é uma função assíncrona, no seu escopo a primeira linha é uma verificação para que o usuário não consiga enviar uma requisição para a API sem preencher os 2 campos, caso algum deles não esteja preenchido é setado uma mensagem de erro.

Caso os campos tenham sido preenchidos irá entrar no bloco do else, que logo no começo temos um try/catch pois vamos usar requisições HTTP de forma assíncrona, portanto precisamos de uma maneira de tratar os possíveis erros, e no catch o que é feito caso haja um erro é setar uma mensagem de erro no state também, já se não ocorrer nenhum erro o fluxo de execução do try é o seguinte:

  1. Atribuir à uma constante o retorno do envio de uma requisição POST para a rota /sessions da API com o email e senha no corpo da requisição;
  2. Caso a requisição de autenticação tenha sido bem sucedida iremos guardar no Async Storage, que é uma API para armazenamento de dados offline do React Native, o token JWT retornado, pois usaremos ele em todas as requisições a partir de agora;
  3. Criar uma constante contendo as instruções para fazer o reset da Stack de navegação, isso para que quando a aplicação for redirecionada para a tela principal não tenha um botão de voltar para o Login;
  4. E por último iremos usar um dispatch nessa constante contendo as instruções de reset.

E nosso componente está funcional e quase pronto, faltou apenas a validação das props através do PropType, para isso podemos adicionar o seguinte trecho entre o navigationOptions e o state:

static propTypes = {
  navigation: PropTypes.shape({
    navigate: PropTypes.func,
    dispatch: PropTypes.func,
  }).isRequired,
};

Esse trecho indica que nesse componente é necessário a passagem de um objeto navigation que contenha as funções navigate e dispatch.

Ufa, agora sim o componente está pronto (finalmente) 😃

Tela Principal

Essa é a tela onde será exibido o Mapa com a lista de propriedades, mas ficará para o próximo post para que esse não se estenda muito, para que não haja erro na aplicação quando o Login for bem sucedido insira apenas o código abaixo no index.js da pasta src/pages/main.

import React from 'react';

import { View } from 'react-native';

// import styles from './styles';

const Main = () => (
  <View />
);

export default Main;

Tela de Cadastro

Para finalizar temos a tela de cadastro, que é muito parecida com a tela de Login, a mudança é apenas na inserção de um terceiro campo e na função de cadastrar, irei postar abaixo como ficou o código do componente, separado pelo código do render, arquivo de estilo e depois as funções.

Como o código do componente ficou um pouco extenso optei por deixá-lo nesse link, basta copiar o código e colar no arquivo src/pages/signUp/index.js.

O código dos estilos você pode encontrar nesse link, basta copiar seu conteúdo para o arquivo src/pages/signUp/styles.js.

Apenas para contextualizá-lo a diferença entre as telas de Login e Cadastro é o que é enviado na requisição, que no Cadastro além de email e senha é enviado o username, na função de Cadastro além da captura de erros tem uma funcionalidade para mostrar uma mensagem caso haja sucesso no cadastro, e dar um delay de 2,5 segundos antes de redirecionar a aplicação para o Login.

A primeira parte acabou, mas logo tem mais

Nesse post vimos como iniciar uma aplicação, configurá-la e criar usando navegação uma comunicação entre a aplicação e uma API desenvolvida com Adonis, nos próximos posts iremos avançar com essa aplicação instalando Mapa e Câmera para implementar novas funcionalidades 😉

Você pode encontrar o código final desse post nesse link.

Ah, não esquece que se tiver gostando dessa série deixa seu comentário aí em baixo que faz toda diferença!! 🙂

Abraços e até o próximo post \o/