Instalando o Mapbox e listando imóveis no React Native

Esse post é a quinta 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.

Nesse ponto já temos a aplicação criada e configurada para se comunicar com a API desenvolvida no começo dessa série, e com isso já conseguimos implementar o processo de autenticação e cadastro de usuário, nessa parte iremos instalar e configurar o Mapbox para podermos fazer o uso de mapas e também buscar da API uma listagem de imóveis cadastrados com base na distância do ponto onde você está.

Iniciando a Aplicação

Caso você tenha entrado direto nessa parte, você pode acessar esse link e baixar o projeto com todas as configurações do post anterior, apesar de eu recomendar pessoalmente que você leia a primeira parte para entender o fluxo da aplicação.

Ou se você já viu a primeira parte pode continuar para a próxima sessão onde iremos começar a instalação do Mapbox.

Instalando e Configurando o Mapbox

Para não deixar o post muito extenso explicando o passo a passo da instalação e configuração do Mapbox, você pode correr no Blog da Rocketseat e ler esse super post do Higo sobre a instalação e configuração do Mapbox.

E para obter o token de acesso indicado no post sobre o Mapbox você pode acessar esse link, use o botão Sign In para fazer login com sua conta ou então se cadastrar, depois de logado basta acessar esse link e ter acesso à seu token 😉

Caso você tenha algum problema na instalação ou não tenha conseguido fazer todas as configurações acesse esse link e baixe a estrutura configurada para dar continuidade.

Usando o Mapbox na Tela Principal

Até no post anterior quando um usuário era autenticado com sucesso na aplicação o código que tínhamos na Tela Principal era:

import React from 'react';

import { View } from 'react-native';

// import styles from './styles';

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

export default Main;

Ainda tem dúvidas ou insegurança com Javascript usando a sintaxe do ES6? Tá esperando o que? Corre lá no site da Rocketseat (entre aqui) e faça os nossos cursos gratuitos sobre Javascript e ES6/ES7/ES8.


Ufa, agora sim vamos inserir o Mapa ocupando a tela toda, para isso primeiro adicione a linha de importação do componente MapboxGL, como abaixo:

import MapboxGL from '@mapbox/react-native-mapbox-gl';

Feito isso troque a View que temos dentro do componente App por:

<MapboxGL.MapView
  centerCoordinate={[-49.6446024, -27.2108001]}
  style={{ flex: 1 }}
  styleURL={MapboxGL.StyleURL.Dark}
/>

O código acima irá inserir na Tela Principal o Mapa ocupando todo espaço disponível, as props passadas para ele são: centerCoordinate, que é usada para indicar qual o ponto que o mapa deve inicializar centralizado e a styleURL que é usada para dar o estilo ao mapa, no nosso caso, um tema escuro.

Obs.: Se você ainda não adicionou o token de acesso do Mapbox em lugar algum, abra o arquivo index.js da pasta src e adicione a importação do Mapbox logo no início seguida da linha abaixo, do contrário o mapa será inserido na tela mas não será exibido.

MapboxGL.setAccessToken('<aqui_vai_seu_token>');

Nesse momento se você executar o aplicativo no emulador/simulador a tela principal deve estar assim:

Para finalizar a Tela Principal falta listar os imóveis cadastrados no banco de dados e também adicionar alguns detalhes ao código, como esconder a navigationBar e modificar a cor da StatusBar.

Primeiramente vamos criar no arquivo styles.js um Container usando a lib Styled Components, o estilo vai ficar assim:

import styled from 'styled-components';
const Container = styled.View`
  flex: 1;
`;
export { Container };

Agora vamos importá-lo no arquivo index.js como já fizemos anteriormente nas outras telas e inseri-lo por volta do componente de Mapa, e para mudar a cor da StatusBar adicione a importação dela no começo do código, dessa maneira:

import { StatusBar } from 'react-native';

E dentro do Container mas acima do componente de Mapa adicione a StatusBar com a prop barStyle="light-content". O código final do index.js deve ficar assim:

import React from 'react';
import PropTypes from 'prop-types';

import { StatusBar } from 'react-native';

import MapboxGL from '@mapbox/react-native-mapbox-gl';

import { Container } from './styles';

const Main = () => (
  <Container>
    <StatusBar barStyle="light-content" />
    <MapboxGL.MapView
      centerCoordinate={[-49.6446024, -27.2108001]}
      style={{ flex: 1 }}
      styleURL={MapboxGL.StyleURL.Dark}
    />
  </Container>
);

Main.navigationOptions = {
  header: null,
};

export default Main;

Você pode ver o código completo da aplicação até esse ponto nesse link.

Buscando a Lista de Imóveis na API

Com o mapa sendo renderizado corretamente vamos agora recuperar a lista de imóveis da API para listá-los no Mapa.

O primeiro passo para essa funcionalidade é fazer uma pequena mudança no componente Main, que para realizar essa busca de imóveis precisaremos fazer uso dos métodos de Ciclo de Vida desse componente, portanto precisamos transformá-lo de um Stateless Component (baseado em constante) para um Stateful Component (baseado em classe), o código do componente transformado fica assim:

export default class Main extends Component {
  static navigationOptions = {
    header: null,
  }

  render() {
    return (
      <Container>
        <StatusBar barStyle="light-content" />
        <MapboxGL.MapView
          centerCoordinate={[-49.6446024, -27.2108001]}
          style={{ flex: 1 }}
          styleURL={MapboxGL.StyleURL.Dark}
        />
      </Container>
    );
  }
}

Com o componente transformado em Stateful Component podemos criar a função que será a responsável por renderizar no mapa a lista de Imóveis que serão buscados da API, logo abaixo função que usaremos para isso, mas antes dela devemos fazer 2 ajustes para que ela funcione corretamente, o primeiro é criar um state no componente contendo a variável locations contendo um array vazio, ficando assim:

state = {
  locations: [],
}

O próximo ajuste é dentro do componente de mapa, devemos ajustá-lo para receber componentes dentro dele, para isso você pode deixá-lo assim:

<MapboxGL.MapView
  centerCoordinate={[-49.6446024, -27.2108001]}
  style={{ flex: 1 }}
  styleURL={MapboxGL.StyleURL.Dark}
>
  { this.renderLocations() }
</MapboxGL.MapView>

Agora sim podemos focar na criação da função renderLocations, que irá apenas fazer um map na variável locations do state e para cada item renderizar uma PointAnnotation no Mapbox, seu código ficou assim:

renderLocations = () => (
  this.state.locations.map(location => (
    <MapboxGL.PointAnnotation
      id={location.id.toString()}
      coordinate={[parseFloat(location.longitude), parseFloat(location.latitude)]}
    >
      <AnnotationContainer>
        <AnnotationText>{location.price}</AnnotationText>
      </AnnotationContainer>
      <MapboxGL.Callout title={location.title}/>
    </MapboxGL.PointAnnotation>
  ))
)

E para que os pontos sejam renderizados corretamente e não haja erro de importação, vamos primeiro no arquivo styles.js e criar os componentes AnnotationContainer e AnnotationText, ficando assim:

import styled from 'styled-components';

const Container = styled.View`
  flex: 1;
`;

const AnnotationContainer = styled.View`
  alignItems: center;
  justifyContent: center;
  backgroundColor: #FC6663;
  borderRadius: 5;
  padding: 5px;
`;

const AnnotationText = styled.Text`
  fontSize: 14px;
  color: #FFF;
`;

export { Container, AnnotationContainer, AnnotationText };

E depois importá-los no index.js da Tela Principal, ficando assim:

import { 
    Container,
    AnnotationContainer,
    AnnotationText 
} from './styles';

Estamos quase terminando essa parte de listagens de imóveis(aleluiaaaaa), temos apenas que adicionar mais um método no arquivo index.js pra terminarmos a busca de imóveis da API, afinal já configuramos para listar os imóveis mas ainda não enviamos a requisição que irá retornar essa lista rsrs.

Para finalizar vamos criar o método componentDidMount no componente Main, ele será o responsável por enviar a requisição para a API assim que o componente for montado, irei postar o código e logo abaixo explico o que está sendo feito:

async componentDidMount() {
    try {
      const response = await api.get('/properties', {
        params: {
          latitude: -27.210768,
          longitude: -49.644018,
        },
      });

      this.setState({ locations: response.data });
    } catch (err) {
      console.tron.log(err);
    }
  }

Você deve ter percebido logo no início da função o async, isso porque esse é o único método do ciclo de vida que pode ser executado de forma assíncrona, dentro dele temos um try/catch para tratativa de erros, e no começo do try já temos a função responsável por enviar a requisição para a API, e como visto nos posts sobre a API para recuperar os imóveis temos que passar as coordenadas do usuário para que sejam trazidos baseado na distância, assim que houver uma resposta da requisição o array é passado para o locations do state.


Bom nesse ponto se tudo deu certo quando você tentar enviar a requisição para a API haverá um erro (Ué, mas como haverá erro se tudo está correto?).

Esse erro acontece pois depois que o usuário foi autenticado temos que enviar o Token JWT em todas as requisições para validar o usuário e retornar um resultado correto, mas não se preocupe, veja a sessão abaixo que vamos corrigir isso (acho bom mesmo).

Implementando Interceptors no Axios para envio do Token JWT

Para garantir que o Axios envie em toda requisição o token JWT no índice Authentication do Header da mesma vamos implementar o recurso de Interceptors.

Você pode ler mais sobre esse recurso diretamente na documentação da lib nesse link.

Para implementar é bem simples, abram o arquivo de configuração do Axios, o api.js, e entre o const api = axios.create(); e o export default api; o seguinte código:

api.interceptors.request.use(async (config) => {
  try {
    const token = await AsyncStorage.getItem('@AirBnbApp:token');

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  } catch (err) {
    alert(err);
  }
});

Esse trecho de código irá interceptar todas as requisições enviadas e antes de repassá-la para a API vai verificar se há no Async Storage o token que nós criamos no momento do Login, caso ele exista será adicionado no Authorization do Header esse Token no formato correto para a API reconhecer e validá-lo, e por último será retornado essa nova config para a request.

Ufa, mais uma parte chega ao fim

Nessa parte da série vimos como utilizar o token retornado da autenticação para buscarmos informações que a API só fornece para um usuário depois de autenticado.

Se você gostou desse post e também da série sobre o AirBnb deixe seu comentário, crítica ou sugestão que é muito importante pra gente.

Aguardo você no próximo post 😉