Fala Samurai, beleza?

Aqui vamos nós com mais um tutorial para React Native.

Dessa vez nós iremos destrinchar outro caso de uso bastante requisitado para desenvolvimento mobile que é o uso de mapas e geolocalização.

Considerações iniciais

Iremos partir de uma aplicação React Native do zero, gerada pelo:

react-native init SamuraiMap

Para esse tutorial, a ideia é trazer uma tela com um mapa com uma View para nos informar a latitude e longitude atual e um botão que ao clicar fará uso da geolocalização para pegar a localização atual e marcar no mapa.

Essa é uma prévia da aplicação:

Imagem prévia da aplicação React Native/SamuraiMap - Dev Samurai

Preparando o layout

Nesse caso vamos trabalhar somente no arquivo App.js.

Como iremos precisar de um ícone para nosso layout, já vamos instalar a lib react-native-vector-icons e faremos o link para utilizá-la:

yarn add react-native-vector-icons

react-native link react-native-vector-icons

Para exibir o mapa, vamos utilizar a lib react-native-maps, então rode o comando:

yarn add react-native-maps

No caso do IOS essa lib pode utilizar o mapa providenciado nativamente.

Mas para o Android temos que utilizar o Google Maps.

Então caso esteja no IOS e queira utilizar o mapa nativo, pode pular essa próxima etapa Gerando uma API-KEY, mas se quiser utilizar o Google Maps no IOS, pode acompanhar.

Gerando uma API-KEY

Acesse o site https://cloud.google.com/console/google/maps-apis/overview, caso não tenha uma conta no Google Cloud Platform, vai precisar criar.

Depois, é preciso criar um novo projeto, pode chamar como quiser, ai dentro do projeto, no menu que pode ser acessado na lateral esquerda, procure por APIS e serviços, deverá chegar em uma tela parecida com essa:

Imagem exemplo painel do google cloud platform - Dev Samurai

Na opção Credentials, crie uma nova chave de api para todos os serviços e não precisa de por com restrições, COPIE ESSA API-KEY GERADA e cole em algum lugar para usar daqui a pouco.

Depois volte para a opção Painel.

Agora podemos clicar em + ATIVAR APIS E SERVIÇOS e depois ativar o Maps SDK for Android se estiver no Android ou Maps SDK for IOS caso esteja no IOS.

Utilizando o MapView

Voltando no projeto React Native.

Caso esteja em Android, vá em andoroid/app/src/main/AndroidManifest.xml e adicione isso dentro da tag application:

<meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="SUA_API_KEY"/>

Vamos adicionar em nosso App.js um MapView com um Marker, ambos da lib react-native-maps:

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

import MapView, {Marker} from 'react-native-maps';

const App = () => {
  const [position, setPosition] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });

  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        region={position}
        onPress={e =>
          setPosition({
            ...position,
            latitude: e.nativeEvent.coordinate.latitude,
            longitude: e.nativeEvent.coordinate.longitude,
          })
        }>
        <Marker
          coordinate={position}
          title={'Marcador'}
          description={'Testando o marcador no mapa'}
        />
      </MapView>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    height: '100%',
    width: '100%',
  },
});

export default App;

O MapView tem suas props e eventos que estão documentados aqui: https://github.com/react-native-community/react-native-maps/blob/master/docs/mapview.md

No nosso app, utilizando a props region e o evento onPress para manipular um valor de state que chamamos de position, onde vai armazenar as coordenadas atuais do mapa, sendo os valores atuais, a região de San Francisco, apenas para nossa demonstração.

OBS: Se estiver utilizando o Google Maps no IOS, adicionar mais uma props no MapView, chamada provider com o valor de google

Rode o comando react-native run-android ou react-native run-ios.

OBS: Se o build falhar, acusando erro nessa linha:

dev_samurai/SamuraiMap/node_modules/react-native-maps/lib/android/build.gradle' line: 20

Acesse esse arquivo descrito e exatamente na linha acusada (no caso a 20), adicione:

def supportLibVersion = safeExtGet('supportLibVersion', '28.0.0')

A aplicação deve apresentar o mapa e o marcador:

Imagem mapa na aplicação React Native/SamuraiMap - Dev Samurai

Adicionando o logo, painel e botão

Agora que já exibimos o mapa, vamos adicionar os outros detalhes do layout.

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

import MapView, {Marker} from 'react-native-maps';
import Icon from 'react-native-vector-icons/MaterialIcons';

const App = () => {
  const [position, setPosition] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });

  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        region={position}
        onPress={e =>
          setPosition({
            ...position,
            latitude: e.nativeEvent.coordinate.latitude,
            longitude: e.nativeEvent.coordinate.longitude,
          })
        }>
        <Marker
          coordinate={position}
          title={'Marcador'}
          description={'Testando o marcador no mapa'}
        />
      </MapView>
      <View style={styles.positonBox}>
        <Text style={styles.positonBoxTitle}>Sua Localização</Text>
        <View style={styles.positonBoxLatLon}>
          <Text style={{fontSize: 18}}>Lat.</Text>
          <Text style={{fontSize: 18}}>{position.latitude}</Text>
        </View>
        <View style={styles.positonBoxLatLon}>
          <Text style={{fontSize: 18}}>Lon.</Text>
          <Text style={{fontSize: 18}}>{position.longitude}</Text>
        </View>
      </View>
      <TouchableOpacity style={styles.locationButton} onPress={() => {}}>
        <Icon name="my-location" color={'#fff'} size={30} />
      </TouchableOpacity>
      <View style={styles.logo}>
        <Text style={styles.logoText}>Samurai</Text>
        <Text style={[styles.logoText, {color: '#e74c3c'}]}>Map</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    height: '100%',
    width: '100%',
  },
  logo: {
    backgroundColor: '#fff',
    borderRadius: 15,
    paddingHorizontal: 15,
    elevation: 5,
    marginTop: -730,
    alignSelf: 'center',
    marginRight: 10,
    flexDirection: 'row',
  },
  logoText: {
    fontWeight: 'bold',
    fontSize: 22,
  },
  positonBox: {
    backgroundColor: '#fff',
    borderRadius: 20,
    opacity: 0.75,
    marginTop: -170,
    marginHorizontal: 40,
    padding: 25,
    shadowColor: '#000',
    elevation: 5,
  },
  positonBoxTitle: {
    textAlign: 'center',
    fontSize: 22,
    fontWeight: 'bold',
    color: '#e74c3c',
  },
  positonBoxLatLon: {flexDirection: 'row', justifyContent: 'space-between'},
  locationButton: {
    backgroundColor: '#e74c3c',
    borderRadius: 150,
    marginTop: -25,
    width: 50,
    height: 50,
    alignSelf: 'center',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    elevation: 8,
  },
});

export default App;

Com isso sua aplicação já deve estar com o layout igual a da prévia.

Agora falta adicionar a Geolocalização e colocar a função no botão.

Adicionando a Geolocalização

O sentido da geolocalização no nosso exemplo é para ao clicar a position do mapa receber a localização atual em que estamos.

Para isso vamos utilizar uma lib chamada react-native-geolocation-service, ela proverá funções para pegar a localização do GPS do celular, por isso, é preciso dar permissão para acessar a nossa localização.

yarn add react-native-geolocation-service

Se estiver no emulador, terá que habilitar o GPS do emulador e definir uma coordenada especifica para simular sua posição atual, no meu caso, como estou utilizando o emulador de Android no Genymotion, a tela de configuração do GPS, é assim:

GPS config no emulador do Genymotion - Dev Samurai

Caso esteja em Android, vá em andoroid/app/src/main/AndroidManifest.xml e adicione isso dentro da tag manifest:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Agora vamos importar o Geolocation da lib react-native-geolocation-service e utilizaremos o método getCurrentPosition para acessar nossa localização atual.

O método getCurrentPosition é assíncrono e podemos usar nesse formato:

Geolocation.getCurrentPosition(
          pos => {
            setPosition({
              ...position,
              latitude: pos.coords.latitude,
              longitude: pos.coords.longitude,
            });
          },
          error => {
            console.log(error);
            Alert.alert('Houve um erro ao pegar a latitude e longitude.');
          },
        );

Iremos utilizar esse método no clique no botão para pegar nossa localização, mas precisamos verificar se o usuário deu permissão para acessar o GPS do celular, por isso, vamos criar um método que fará essa verificação e depois pega a posição atual:

import React, {useState} from 'react';
import {
  PermissionsAndroid,
  Alert,
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
} from 'react-native';

import MapView, {Marker} from 'react-native-maps';
import Geolocation from 'react-native-geolocation-service';
import Icon from 'react-native-vector-icons/MaterialIcons';

const App = () => {
  const [position, setPosition] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });

  const request_location_runtime_permission = async () => {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        {
          title: 'Permissão de Localização',
          message: 'A aplicação precisa da permissão de localização.',
        },
      );
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        Geolocation.getCurrentPosition(
          pos => {
            setPosition({
              ...position,
              latitude: pos.coords.latitude,
              longitude: pos.coords.longitude,
            });
          },
          error => {
            console.log(error);
            Alert.alert('Houve um erro ao pegar a latitude e longitude.');
          },
        );
      } else {
        Alert.alert('Permissão de localização não concedida');
      }
    } catch (err) {
      console.log(err);
    }
  };

  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        region={position}
        onPress={e =>
          setPosition({
            ...position,
            latitude: e.nativeEvent.coordinate.latitude,
            longitude: e.nativeEvent.coordinate.longitude,
          })
        }>
        <Marker
          coordinate={position}
          title={'Marcador'}
          description={'Testando o marcador no mapa'}
        />
      </MapView>
      <View style={styles.positonBox}>
        <Text style={styles.positonBoxTitle}>Sua Localização</Text>
        <View style={styles.positonBoxLatLon}>
          <Text style={{fontSize: 18}}>Lat.</Text>
          <Text style={{fontSize: 18}}>{position.latitude}</Text>
        </View>
        <View style={styles.positonBoxLatLon}>
          <Text style={{fontSize: 18}}>Lon.</Text>
          <Text style={{fontSize: 18}}>{position.longitude}</Text>
        </View>
      </View>
      <TouchableOpacity
        style={styles.locationButton}
        onPress={() => {
          request_location_runtime_permission();
        }}>
        <Icon name="my-location" color={'#fff'} size={30} />
      </TouchableOpacity>
      <View style={styles.logo}>
        <Text style={styles.logoText}>Samurai</Text>
        <Text style={[styles.logoText, {color: '#e74c3c'}]}>Map</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    height: '100%',
    width: '100%',
  },
  logo: {
    backgroundColor: '#fff',
    borderRadius: 15,
    paddingHorizontal: 15,
    elevation: 5,
    marginTop: -730,
    alignSelf: 'center',
    marginRight: 10,
    flexDirection: 'row',
  },
  logoText: {
    fontWeight: 'bold',
    fontSize: 22,
  },
  positonBox: {
    backgroundColor: '#fff',
    borderRadius: 20,
    opacity: 0.75,
    marginTop: -170,
    marginHorizontal: 40,
    padding: 25,
    shadowColor: '#000',
    elevation: 5,
  },
  positonBoxTitle: {
    textAlign: 'center',
    fontSize: 22,
    fontWeight: 'bold',
    color: '#e74c3c',
  },
  positonBoxLatLon: {flexDirection: 'row', justifyContent: 'space-between'},
  locationButton: {
    backgroundColor: '#e74c3c',
    borderRadius: 150,
    marginTop: -25,
    width: 50,
    height: 50,
    alignSelf: 'center',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    elevation: 8,
  },
});

export default App;

Rode o comando react-native run-android ou react-native run-ios.

OBS: Pode ser que o build venha a falhar novamente, acusando erro nessa linha:

dev_samurai/SamuraiMap/node_modules/react-native-maps/lib/android/build.gradle' line: 20

Acesse esse arquivo descrito e exatamente na linha acusada (no caso a 20), adicione:

def supportLibVersion = safeExtGet('supportLibVersion', '28.0.0')

Depois só rodar o build novamente.

Agora ao clicar no botão para pegar a localização atual, a primeira vez deverá pedir o acesso e depois irá levar o marcador do mapa para a localização atual \O. Aplicação final, pegando minha localização atual:

Imagem final da aplicação React Native/SamuraiMap - Dev Samurai

Conclusão

Chegamos ao final de mais um tutorial de libs, ferramentas em React Native, nesse tutorial tivemos algumas configurações especiais e um problema de build que foi necessário lidar.

Essas nuances e problemas são perfeitamente normais na vida de um programador e principalmente quando lidamos com ferramentas open-source e que estão em constante atualização.

E como a intenção desses tutoriais e fazer o passo a passo definitivo, exatamente do jeito que nós aprendemos a utilizar as ferramentas aqui na Dev Samurai, também trazemos os problemas que tivemos.

A partir desses labs (como gosto de chamar esses testes de ferramentas) que aprendemos e é assim que depois saímos utilizando e criando aplicações mais complexas e robustas.

Espero que tenham gostado do tutorial e caso não tenham visto sobre utilização da câmera do celular com React Native, acessem aqui: React Native Tutorials: Usando a Câmera do Celular

E não deixe de participar da nossa comunidade no Discord, lá podemos discutir sobre diversos assuntos relacionados a programação e os cursos do Dev Samurai, você vai poder encontrar com o pessoal da equipe Dev Samurai e também toda comunidade que participa e esta engajada em aprender a fazer aplicativos e discutir sobre programação, então segue o link:

Comunidade Dev Samurai no Discord.

Segue o link do repositório para você baixar o código: https://mautic.devsamurai.com.br/asset/12:tutorial-samuraimap

Valeu e até a próxima!