how to mock a prop that is a callback that updates state? - react-native

this is my code:
const [userRegion, setUserRegion] = useState<Region | null>(null);
<MapView
{...{
...(!!myLocation && {
initialRegion: {
latitude: myLocation.coords.latitude,
longitude: myLocation.coords.longitude,
latitudeDelta: 0.0043,
longitudeDelta: 0.0034,
},
}),
onRegionChangeComplete: (region: Region) => {
console.log('onRegionChangeComplete region: ', region);
if (userRegion === null) setUserRegion(region);
},
}}>
i am having a hard time making test for onRegionChangeComplete prop on MapView, region is a default argument that is calculated when initialRegion is valid, then onRegionChangeComplete is fired, but in jest even when there's initialRegion, its' region is null. i need to make region with proper value like: {"latitude": -0.000011399388312454881, "latitudeDelta": 0.0004928559064869245, "longitude": 0, "longitudeDelta": 0.00026151537895202637}
but how?
this is my react-native-maps on my jest.setup:
jest.mock('react-native-maps', () => {
const mockMapTypes = {
STANDARD: 0,
SATELLITE: 1,
HYBRID: 2,
TERRAIN: 3,
NONE: 4,
MUTEDSTANDARD: 5,
};
return {
__esModule: true,
default: MockMapView,
Marker: MockMapMarkerView,
MAP_TYPES: mockMapTypes,
PROVIDER_DEFAULT: 'default',
PROVIDER_GOOGLE: 'google',
};
});
for the MockMapView this is what i tried to mimic a callback for region and i get value for region but it throws warning:
Warning: Cannot update a component (Nearby) while rendering a
different component (MockMapView). To locate the bad setState() call
inside MockMapView, follow the stack trace as described in
https://reactjs.org/link/setstate-in-render
export class MockMapView extends React.Component<MockMapProps> {
render() {
const { testID, onRegionChangeComplete, children, ...props } = this.props;
return (
<View
{...{
testID,
onRegionChangeComplete: jest.fn().mockReturnValue(
onRegionChangeComplete({
latitude: 11.559064518910894,
latitudeDelta: 0.006275210816060195,
longitude: 114.52057879418135,
longitudeDelta: 0.003400370478630066,
})
),
...props,
}}>
{children}
</View>
);
}
}

I have tried something like this
jest.mock('react-native-maps', () => {
const React = require('react');
const {View} = require('react-native');
const {jest} = require('#jest/globals');
const mockOnRegionChange = jest.fn().mockImplementation(() => {
return {latitude: 57.7, longitude: 11.93};
});
class MockMapView extends React.Component {
render() {
const {testID, children, ...props} = this.props;
return (
<View
{...{
...props, // <======= Moved this to the Top
testID,
onRegionChangeComplete: mockOnRegionChange,
}}>
{children}
</View>
);
}
}
const mockMapTypes = {
STANDARD: 0,
SATELLITE: 1,
HYBRID: 2,
TERRAIN: 3,
NONE: 4,
MUTEDSTANDARD: 5,
};
return {
__esModule: true,
default: MockMapView,
MAP_TYPES: mockMapTypes,
PROVIDER_DEFAULT: 'default',
PROVIDER_GOOGLE: 'google',
};
});
And I get this as result on debug() with React Testing Library. It says onRegionChangeComplete={[Function mockConstructor]}

Related

The best way of tracking location in background using react-native + Expo in 2020

I want to create my own Endomono/Runtastic-like app using RN + expo (This app will be just for me, and I have android phone with pretty decent performance/battery life (Redmi note 7) so I don't worry about performance too much). I wanted to use all-in-one library for that, or just and library that allows me to execute some code each X seconds in background (and getAsyncLocation there). My point is just to send lat/lon data every X seconds to my backend HTTP django-rest-framework powered server.
I just spent whole day trying figure out any way to do that, I tried couple of libraries like this ones: react-native-background-geolocation, react-native-background-timer, react-native-background-job and few more. I followed step by step instalation guide, and I kept getting errors like: null is not an object (evaluating 'RNBackgroundTimer.setTimeout') .
I also tried this: I fixed some errors in this code (imports related), it seemed to work, but when I changed my GPS location using Fake GPS, and only one cast of didFocus functions appears in the console. Here's code:
import React from 'react';
import { EventEmitter } from 'fbemitter';
import { NavigationEvents } from 'react-navigation';
import { AppState, AsyncStorage, Platform, StyleSheet, Text, View, Button } from 'react-native';
import MapView from 'react-native-maps';
import * as Permissions from 'expo-permissions';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import { FontAwesome, MaterialIcons } from '#expo/vector-icons';
const STORAGE_KEY = 'expo-home-locations';
const LOCATION_UPDATES_TASK = 'location-updates';
const locationEventsEmitter = new EventEmitter();
export default class MapScreen extends React.Component {
static navigationOptions = {
title: 'Background location',
};
mapViewRef = React.createRef();
state = {
accuracy: 4,
isTracking: false,
showsBackgroundLocationIndicator: false,
savedLocations: [],
initialRegion: null,
error: null,
};
didFocus = async () => {
console.log("Hello")
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
AppState.addEventListener('change', this.handleAppStateChange);
this.setState({
error:
'Location permissions are required in order to use this feature. You can manually enable them at any time in the "Location Services" section of the Settings app.',
});
return;
} else {
this.setState({ error: null });
}
const { coords } = await Location.getCurrentPositionAsync();
console.log(coords)
const isTracking = await Location.hasStartedLocationUpdatesAsync(LOCATION_UPDATES_TASK);
const task = (await TaskManager.getRegisteredTasksAsync()).find(
({ taskName }) => taskName === LOCATION_UPDATES_TASK
);
const savedLocations = await getSavedLocations();
const accuracy = (task && task.options.accuracy) || this.state.accuracy;
this.eventSubscription = locationEventsEmitter.addListener('update', locations => {
this.setState({ savedLocations: locations });
});
if (!isTracking) {
alert('Click `Start tracking` to start getting location updates.');
}
this.setState({
accuracy,
isTracking,
savedLocations,
initialRegion: {
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
},
});
};
handleAppStateChange = nextAppState => {
if (nextAppState !== 'active') {
return;
}
if (this.state.initialRegion) {
AppState.removeEventListener('change', this.handleAppStateChange);
return;
}
this.didFocus();
};
componentWillUnmount() {
if (this.eventSubscription) {
this.eventSubscription.remove();
}
AppState.removeEventListener('change', this.handleAppStateChange);
}
async startLocationUpdates(accuracy = this.state.accuracy) {
await Location.startLocationUpdatesAsync(LOCATION_UPDATES_TASK, {
accuracy,
showsBackgroundLocationIndicator: this.state.showsBackgroundLocationIndicator,
});
if (!this.state.isTracking) {
alert(
'Now you can send app to the background, go somewhere and come back here! You can even terminate the app and it will be woken up when the new significant location change comes out.'
);
}
this.setState({ isTracking: true });
}
async stopLocationUpdates() {
await Location.stopLocationUpdatesAsync(LOCATION_UPDATES_TASK);
this.setState({ isTracking: false });
}
clearLocations = async () => {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify([]));
this.setState({ savedLocations: [] });
};
toggleTracking = async () => {
await AsyncStorage.removeItem(STORAGE_KEY);
if (this.state.isTracking) {
await this.stopLocationUpdates();
} else {
await this.startLocationUpdates();
}
this.setState({ savedLocations: [] });
};
onAccuracyChange = () => {
const next = Location.Accuracy[this.state.accuracy + 1];
const accuracy = next ? Location.Accuracy[next] : Location.Accuracy.Lowest;
this.setState({ accuracy });
if (this.state.isTracking) {
// Restart background task with the new accuracy.
this.startLocationUpdates(accuracy);
}
};
toggleLocationIndicator = async () => {
const showsBackgroundLocationIndicator = !this.state.showsBackgroundLocationIndicator;
this.setState({ showsBackgroundLocationIndicator }, async () => {
if (this.state.isTracking) {
await this.startLocationUpdates();
}
});
};
onCenterMap = async () => {
const { coords } = await Location.getCurrentPositionAsync();
const mapView = this.mapViewRef.current;
if (mapView) {
mapView.animateToRegion({
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
});
}
};
renderPolyline() {
const { savedLocations } = this.state;
if (savedLocations.length === 0) {
return null;
}
return (
<MapView.Polyline
coordinates={savedLocations}
strokeWidth={3}
strokeColor={"black"}
/>
);
}
render() {
if (this.state.error) {
return <Text style={styles.errorText}>{this.state.error}</Text>;
}
if (!this.state.initialRegion) {
return <NavigationEvents onDidFocus={this.didFocus} />;
}
return (
<View style={styles.screen}>
<MapView
ref={this.mapViewRef}
style={styles.mapView}
initialRegion={this.state.initialRegion}
showsUserLocation>
{this.renderPolyline()}
</MapView>
<View style={styles.buttons} pointerEvents="box-none">
<View style={styles.topButtons}>
<View style={styles.buttonsColumn}>
{Platform.OS === 'android' ? null : (
<Button style={styles.button} onPress={this.toggleLocationIndicator} title="background/indicator">
<Text>{this.state.showsBackgroundLocationIndicator ? 'Hide' : 'Show'}</Text>
<Text> background </Text>
<FontAwesome name="location-arrow" size={20} color="white" />
<Text> indicator</Text>
</Button>
)}
</View>
<View style={styles.buttonsColumn}>
<Button style={styles.button} onPress={this.onCenterMap} title="my location">
<MaterialIcons name="my-location" size={20} color="white" />
</Button>
</View>
</View>
<View style={styles.bottomButtons}>
<Button style={styles.button} onPress={this.clearLocations} title="clear locations">
Clear locations
</Button>
<Button style={styles.button} onPress={this.toggleTracking} title="start-stop tracking">
{this.state.isTracking ? 'Stop tracking' : 'Start tracking'}
</Button>
</View>
</View>
</View>
);
}
}
async function getSavedLocations() {
try {
const item = await AsyncStorage.getItem(STORAGE_KEY);
return item ? JSON.parse(item) : [];
} catch (e) {
return [];
}
}
if (Platform.OS !== 'android') {
TaskManager.defineTask(LOCATION_UPDATES_TASK, async ({ data: { locations } }) => {
if (locations && locations.length > 0) {
const savedLocations = await getSavedLocations();
const newLocations = locations.map(({ coords }) => ({
latitude: coords.latitude,
longitude: coords.longitude,
}));
savedLocations.push(...newLocations);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(savedLocations));
locationEventsEmitter.emit('update', savedLocations);
}
});
}
const styles = StyleSheet.create({
screen: {
flex: 1,
},
mapView: {
flex: 1,
},
buttons: {
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
padding: 10,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
},
topButtons: {
flexDirection: 'row',
justifyContent: 'space-between',
},
bottomButtons: {
flexDirection: 'column',
alignItems: 'flex-end',
},
buttonsColumn: {
flexDirection: 'column',
alignItems: 'flex-start',
},
button: {
paddingVertical: 5,
paddingHorizontal: 10,
marginVertical: 5,
},
errorText: {
fontSize: 15,
color: 'rgba(0,0,0,0.7)',
margin: 20,
},
});
If you know any way to easily complete my target (of sending simple HTTP GET with location from background of Expo + RN app to my DRF backend) please let me know.
If you're using Expo you can simply use expo-task-manager and expo-location to get background location updates.
Here's a simplified version that I'm using (and it's working for sure on Android) on the App I'm currently developing:
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import axios from 'axios';
const TASK_FETCH_LOCATION = 'TASK_FETCH_LOCATION';
// 1 define the task passing its name and a callback that will be called whenever the location changes
TaskManager.defineTask(TASK_FETCH_LOCATION, async ({ data: { locations }, error }) => {
if (error) {
console.error(error);
return;
}
const [location] = locations;
try {
const url = `https://<your-api-endpoint>`;
await axios.post(url, { location }); // you should use post instead of get to persist data on the backend
} catch (err) {
console.error(err);
}
});
// 2 start the task
Location.startLocationUpdatesAsync(TASK_FETCH_LOCATION, {
accuracy: Location.Accuracy.Highest,
distanceInterval: 1, // minimum change (in meters) betweens updates
deferredUpdatesInterval: 1000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'Using your location',
notificationBody: 'To turn off, go back to the app and switch something off.',
},
});
// 3 when you're done, stop it
Location.hasStartedLocationUpdatesAsync(TASK_FETCH_LOCATION).then((value) => {
if (value) {
Location.stopLocationUpdatesAsync(TASK_FETCH_LOCATION);
}
});
It doesn't necessarily work with Expo, but if "eject" your project or start with the React Native CLI (via react-native init) then you could use an Android specific React Native "NativeModule" to accomplish your goal. I like using the react-native-location package, which has great support on iOS for background location updates, but on Android there is a bug currently. I put together an example project which has the necessary Android specific code inside a NativeModule you could use to start from:
https://github.com/andersryanc/ReactNative-LocationSample

get current user location using google maps api with react native?

i'm currently working on a project using google maps api with react native, but i'm struggling to handle get current user location with redux, this is my action ,how can i fix it :
import MAPSActionTypes from "./mapsactiontypes";
export const currentlocation = () => {
return navigator.geolocation.getCurrentPosition(
(position) => {
console.log("wokeeey");
console.log(position);
{payload: position}
},
(error) => console.log(error)),
{ enableHighAccuracy: false, timeout: 200000, maximumAge: 1000 },
};
I'm not sure what you're error is exactly, but I found an issue here on doing the exact same thing. It referred to a code sample you can find here, which I've reproduced below.
import React from 'react';
import {
Alert,
Platform,
StyleSheet
} from 'react-native';
import MapView from 'react-native-maps'
const LATITUDE_DELTA = 0.01;
const LONGITUDE_DELTA = 0.01;
const initialRegion = {
latitude: -37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}
class MyMapView extends React.Component {
map = null;
state = {
region: {
latitude: -37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
},
ready: true,
filteredMarkers: []
};
setRegion(region) {
if(this.state.ready) {
setTimeout(() => this.map.mapview.animateToRegion(region), 10);
}
//this.setState({ region });
}
componentDidMount() {
console.log('Component did mount');
this.getCurrentPosition();
}
getCurrentPosition() {
try {
navigator.geolocation.getCurrentPosition(
(position) => {
const region = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
};
this.setRegion(region);
},
(error) => {
//TODO: better design
switch (error.code) {
case 1:
if (Platform.OS === "ios") {
Alert.alert("", "Para ubicar tu locación habilita permiso para la aplicación en Ajustes - Privacidad - Localización");
} else {
Alert.alert("", "Para ubicar tu locación habilita permiso para la aplicación en Ajustes - Apps - ExampleApp - Localización");
}
break;
default:
Alert.alert("", "Error al detectar tu locación");
}
}
);
} catch(e) {
alert(e.message || "");
}
};
onMapReady = (e) => {
if(!this.state.ready) {
this.setState({ready: true});
}
};
onRegionChange = (region) => {
console.log('onRegionChange', region);
};
onRegionChangeComplete = (region) => {
console.log('onRegionChangeComplete', region);
};
render() {
const { region } = this.state;
const { children, renderMarker, markers } = this.props;
return (
<MapView
showsUserLocation
ref={ map => { this.map = map }}
data={markers}
initialRegion={initialRegion}
renderMarker={renderMarker}
onMapReady={this.onMapReady}
showsMyLocationButton={false}
onRegionChange={this.onRegionChange}
onRegionChangeComplete={this.onRegionChangeComplete}
style={StyleSheet.absoluteFill}
textStyle={{ color: '#bc8b00' }}
containerStyle={{backgroundColor: 'white', borderColor: '#BC8B00'}}>
{markers.map(renderMarker)}
{children && children || null}
</ClusteredMapView>
);
}
}
export default MyMapView;

How to render markers on map fetched from API?

I need your help to know how to render markers on map fetched from API.
I want to track a person's location from gps coordinates (longitude and latitude) by getting data from API.
I tried too much to recover the coordinates in the MapView but I did not arrive to do it.
This is my code :
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import MapView from 'react-native-maps';
export default class App extends React.Component {
mapMarkers() {
return fetch('http://first-ontheweb.com/onLineSenior/pos.php')
.then(response => response.json())
.then(responseJson => {
this.setState({
marker: responseJson.data,
});
console.log(responseJson);
return responseJson.data;
})
.catch(error => {
console.error(error);
});
}
componentDidMount() {
this.mapMarkers();
navigator.geolocation.getCurrentPosition(
position => {
console.log('getCurrentPosition Succes');
this.setState({
region: {
...this.state.region,
latitude: position.coords.latitude,
longitude: position.coords.longitude,
},
});
this.watchPosition();
},
error => {
this.props.displayError('Error detecting your Location');
alert(JSON.stringify(error));
},
{ enableHightAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
watchPosition() {
this.watchID = navigator.geolocation.watchPosition(
position => {
console.log('Watch Position Succes');
if (this.props.followUser) {
this.map.animateToRegion(
this.newRegion(position.coords.latitude, position.coords.longitude)
);
}
},
error => {
this.props.displayError('Error detecting your location');
},
{ enableHightAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
componentWillUnmount() {
navigator.geolocation.clearWatch(this.watchID);
}
onRegionChange(region) {
this.setState({ region });
}
constructor(props) {
super(props);
this.state = {
region: {
latitude: -6.914744,
longitude: 107.60981,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
},
markers: [
{
latlng: { latitude: marker.latitude, longitude: marker.longitude },
},
],
};
this.onRegionChange = this.onRegionChange.bind(this);
}
render() {
return (
<MapView
style={styles.map}
showsUserLocation={true}
followUserLocation={true}
zoomEnabled={true}
//annotations={markers}
>
{this.state.markers.map((marker, index) => (
<MapView.Marker
key={index}
coordinate={marker.latlng} />
))}
</MapView>
);
}
}
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
},
});
This is the error:
ESLint: (76:59) 'marker' is not defined. (no-undef)
So, I want to display the GPS coordinates in the MapView by getting data from API.
I think its a typo
in setState it should be markers not marker
this.setState({
markers: responseJson.data,
});
Try this code, I have changed it, now working fine
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import MapView from 'react-native-maps';
export default class App extends React.Component {
mapMarkers() {
return fetch('http://first-ontheweb.com/onLineSenior/pos.php')
.then(response => response.json())
.then(responseJson => {
this.setState({
markers: responseJson,
});
console.log(responseJson);
return responseJson;
})
.catch(error => {
console.error(error);
});
}
componentDidMount() {
this.mapMarkers();
navigator.geolocation.getCurrentPosition(
position => {
console.log('getCurrentPosition Succes');
this.setState({
region: {
...this.state.region,
latitude: position.coords.latitude,
longitude: position.coords.longitude,
},
});
this.watchPosition();
},
error => {
this.props.displayError('Error detecting your Location');
alert(JSON.stringify(error));
},
{ enableHightAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
watchPosition() {
this.watchID = navigator.geolocation.watchPosition(
position => {
console.log('Watch Position Succes');
if (this.props.followUser) {
this.map.animateToRegion(
this.newRegion(position.coords.latitude, position.coords.longitude)
);
}
},
error => {
this.props.displayError('Error detecting your location');
},
{ enableHightAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
componentWillUnmount() {
navigator.geolocation.clearWatch(this.watchID);
}
onRegionChange(region) {
this.setState({ region });
}
constructor(props) {
super(props);
this.state = {
region: {
latitude: -6.914744,
longitude: 107.60981,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
},
markers: []
};
this.onRegionChange = this.onRegionChange.bind(this);
}
render() {
console.log('marker', this.state.markers)
return (
<MapView
initialRegion={this.state.region}
style={styles.map}
showsUserLocation={true}
followUserLocation={true}
showsMyLocationButton={true}
//annotations={markers}
>
{this.state.markers && this.state.markers.map((marker, index) => (
<MapView.Marker
key={index}
coordinate={{
latitude: Number(marker.latitude),
longitude: Number(marker.longitude),
}}
/>
))}
</MapView>
);
}
}
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
},
});

ReadableNativeMap cannot be cast to java.lang.String

I'm using Expo together with react-native-maps for a rather simple map component. It works fine on iOS, however on Android I get the following error:
abi30_0_0.com.facebook.react.bridge.ReadableNativeMap cannot be cast to java.lang.String
getString
ReadableNativeMap.java:168
showAlert
DialogModule.java:247
invoke
Method.java
invoke
JavaMethodWrapper.java:372
invoke
JavaModuleWrapper.java:160
run
NativeRunnable.java
handleCallback
Handler.java:790
dispatchMessage
Handler.java:99
dispatchMessage
MessageQueueThreadHandler.java:29
loop
Looper.java:164
run
MessageQueueThreadImpl.java:192
run
Thread.java:764
Here is my map component (sorry it's a bit longer):
import React from 'react';
import {
StyleSheet,
View,
Dimensions,
Alert,
TouchableOpacity,
Text,
Platform,
} from 'react-native';
import {
MapView,
Location,
Permissions,
Constants,
} from 'expo';
import { Ionicons } from '#expo/vector-icons';
import axios from 'axios';
import geolib from 'geolib';
import Polyline from '#mapbox/polyline';
import api from '../helpers/api';
import appConfig from '../app.json';
const { width, height } = Dimensions.get('window');
class MapScreen extends React.Component {
static navigationOptions = {
title: 'Map',
};
constructor(props) {
super(props);
this.state = {
coordinates: [],
focusedLocation: {
latitude: 0,
longitude: 0,
latitudeDelta: 0.0122,
longitudeDelta: width / height * 0.0122,
},
destinationReached: false,
isMapReady: false,
};
this.apikey = appConfig.expo.android.config.googleMaps.apiKey;
// bind this in constructor so state can be set in these methods
this.getLocation = this.getLocation.bind(this);
this.getDirections = this.getDirections.bind(this);
this.checkUserLocation = this.checkUserLocation.bind(this);
this.animateToCoordinates = this.animateToCoordinates.bind(this);
}
async componentDidMount() {
// ask the user for location permission
if (Platform.OS === 'android' && !Constants.isDevice) {
Alert.alert('Warning', 'This will not work on sketch in an android emulator. Try it on your device!');
return;
}
if (await !this.isPermissionGranted(Permissions.LOCATION)) {
Alert.alert('Permission', 'You need to enable location services');
return;
}
// get the current location of the user
// retrieve the destination location where the users shift will start
const [currentLocation, destinationLocation] = await Promise.all([
this.getLocation(),
this.getInterceptionCoords(),
]);
// retrieve a direction between these two points
this.getDirections(currentLocation, destinationLocation);
// monitor the current position of the user
this.watchid = await Location.watchPositionAsync({
enableHighAccuracy: true,
distanceInterval: 1,
}, this.checkUserLocation);
}
componentWillUnmount() {
if (this.watchid) {
this.watchid.remove();
}
}
/**
* retrieve current coordinates and move to them on the map
* assumes that location permission has already been granted
* #returns {Promise<{latitude: (number|*|string), longitude: (number|*|string)}>}
*/
async getLocation() {
// get current position if permission has been granted
const { coords } = await Location.getCurrentPositionAsync({
enableHighAccuracy: true,
});
// initalize map at current position
this.animateToCoordinates(coords);
this.setState(prevState => {
return {
focusedLocation: {
...prevState.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude,
},
};
});
return {
latitude: coords.latitude,
longitude: coords.longitude,
};
}
/**
* retrieves the coordinates of a route
* route: safety drivers position to the interception point
* #param startLoc
* #param destinationLoc
* #returns {Promise<*>}
*/
async getDirections(startLoc, destinationLoc) {
try {
const response = await axios({
method: 'GET',
url: 'https://maps.googleapis.com/maps/api/directions/json',
params: {
origin: Object.values(startLoc).join(','),
destination: Object.values(destinationLoc).join(','),
key: this.apikey,
},
responseType: 'json',
headers: {},
});
if (response.status !== 200) {
// this will execute the catch block
throw new Error('Fetching the coordinates of the interception point failed');
}
const { data } = response;
if (data.status !== 'OK') {
throw new Error('Determining a route between the two points failed');
}
const points = Polyline.decode(data.routes[0].overview_polyline.points);
const coordinates = points.map(point => {
return {
latitude: point[0],
longitude: point[1],
};
});
this.setState({ coordinates: coordinates });
return coordinates;
} catch (error) {
console.log(error);
Alert.alert('Network error', error);
return error;
}
}
/**
* get the coordinates of the interception point
* #returns {Promise<*>}
*/
async getInterceptionCoords() {
try {
const response = await api.get('/shifts/next');
if (response.status !== 200) {
// this will execute the catch block
throw new Error('Fetching the coordinates of the interception point failed');
}
const { data } = response;
return {
latitude: data.latStart,
longitude: data.longStart,
};
} catch (error) {
console.log(error);
Alert.alert('Network error', error);
return error;
}
}
checkUserLocation(location) {
const { coordinates } = this.state;
const { coords } = location;
if (Platform.OS === 'android') {
// follow the user location
// mapview component handles this for ios devices
this.animateToCoordinates(coords);
}
const destinationCoords = coordinates[coordinates.length - 1];
const distance = geolib.getDistance(coords, destinationCoords);
if (distance <= 20) {
// distance to destination is shorter than 20 metres
// show button so user can confirm arrival
this.setState({ destinationReached: true });
} else {
// remove arrival button in case the user moves away from the destination
this.setState({ destinationReached: false });
}
}
/**
* animate to specified coordinates on the map
* #param coords
*/
animateToCoordinates(coords) {
const { focusedLocation } = this.state;
const { latitude, longitude } = coords;
if (focusedLocation && latitude && longitude) {
this.map.animateToRegion({
...focusedLocation,
latitude: latitude,
longitude: longitude,
});
}
}
renderConfirmalButton() {
const { destinationReached } = this.state;
if (!destinationReached) {
return null;
}
return (
<View style={styles.confirmContainer}>
<TouchableOpacity
style={styles.confirmButton}
onPress={this.onArrivalConfirmed}
>
<View style={styles.drawerItem}>
<Ionicons
name="ios-checkmark-circle-outline"
size={30}
color="#ffffff"
style={styles.drawerItemIcon}
/>
<Text style={styles.buttonText}>Confirm Arrival</Text>
</View>
</TouchableOpacity>
</View>
);
}
isPermissionGranted = async permission => {
const { status } = await Permissions.askAsync(permission);
return (status === 'granted');
};
onArrivalConfirmed = () => {
Alert.alert('Confirmation', 'Arrival confirmed');
};
onMapReady = () => {
this.setState({ isMapReady: true });
};
render() {
const { coordinates, focusedLocation, isMapReady } = this.state;
return (
<View style={styles.container}>
<MapView
style={styles.map}
initialRegion={focusedLocation}
showsUserLocation
followsUserLocation={Platform.OS === 'ios'}
loadingEnabled
ref={map => { this.map = map; }}
onMapReady={() => this.onMapReady()}
>
<MapView.Polyline
coordinates={coordinates}
strokeWidth={3}
strokeColor="blue"
/>
{isMapReady && coordinates.length > 0 && (
<MapView.Marker
coordinate={coordinates[coordinates.length - 1]}
/>
)}
</MapView>
{this.renderConfirmalButton()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
map: {
width: width,
height: height,
},
confirmContainer: {
position: 'absolute',
left: 0,
bottom: 0,
height: 150,
width: '100%',
justifyContent: 'center',
},
confirmButton: {
paddingHorizontal: 30,
},
drawerItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
backgroundColor: 'lightblue',
borderRadius: 15,
},
drawerItemIcon: {
marginRight: 10,
},
buttonText: {
color: '#ffffff',
fontSize: 22,
},
});
export default MapScreen;
Any help is greatly appreciated!

Problems returning address through geolocation

I'm trying to use the react-native-geocoder library to return the address through the latitude and longitude of the device.
Through response to another question and some more research, I came up with this code:
import React, { Component } from 'react';
import {
AppRegistry,
View,
Text
} from 'react-native';
import Geocoder from 'react-native-geocoder'; // 0.5.0
Geocoder.apiKey = '__API__KEY__';
export default class testeGeocoder extends Component {
constructor(props) {
super(props);
this.state = {
latitude: null,
longitude: null,
place: 'Localizando endereço...',
error: null,
};
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
);
Geocoder.geocodePosition(this.state.latitude,this.state.longitude)
.then(res => {
this.setState({
place: res[0].formatedAddress
});
console.log(res[0].formattedAddress)
});
}
render() {
return (
<View style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Latitude: {this.state.latitude}</Text>
<Text>Longitude: {this.state.longitude}</Text>
<Text>{this.state.place.toString()}</Text>
{this.state.error ? <Text>Error: {this.state.error}</Text> : null}
</View>
);
}
}
AppRegistry.registerComponent('testeGeocoder', () => testeGeocoder);
But this returns me to the correct latitude and longitude, but stays locating address ... and never returns.
Edit:
With the help of bennygenel and Michael Cheng I managed to eliminate the warning and got to this code:
import React, { Component } from 'react';
import {
AppRegistry,
View,
Text
} from 'react-native';
import Geocoder from 'react-native-geocoder'; // 0.5.0
Geocoder.apiKey = '__API__KEY__';
export default class teste47 extends Component {
constructor(props) {
super(props);
this.state = {
latitude: null,
longitude: null,
place: 'Localizando endereço...',
error: null,
};
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
position => {
this.setState(
{
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
},
() => {
Geocoder.geocodePosition({
lat: position.coords.latitude,
lng: position.coords.longitude
}).then(res => {
this.setState({
place: res[0].formattedAddress,
});
});
}
);
},
error => this.setState({ error: error.message }),
{
enableHighAccuracy: true, timeout: 20000
});
}
render() {
return (
<View style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Latitude: {this.state.latitude}</Text>
<Text>Longitude: {this.state.longitude}</Text>
<Text>{this.state.place.toString()}</Text>
{this.state.error ? <Text>Error: {this.state.error}</Text> : null}
</View>
);
}
}
AppRegistry.registerComponent('teste47', () => teste47);
But when I execute this it is returning the error:
Geocoder.geocodePosition takes an object contains lat and long. You are trying to send 2 separate parameters.
If you change
Geocoder.geocodePosition(this.state.latitude, this.state.longitude)
.then(res = > {
this.setState({
place: res[0].formattedAddress
});
console.log(res[0].formattedAddress)
});
to this
Geocoder.geocodePosition({ lat: this.state.latitude, long: this.state.longitude})
.then(res = > {
this.setState({
place: res[0].formattedAddress
});
console.log(res[0].formattedAddress)
});
error will be solved.
Side Note 1: When using Promise it is a really good practice to handle error with catch.
Geocoder.geocodePosition({ lat: this.state.latitude, long: this.state.longitude})
.then(res = > {
// do something with response
})
.catch((error) => {
// do something with error
});
Side Note 2: You also have formatedAddress misspelled. It should be corrected to formattedAddress.
Side Note 3: setState() is asynchronous. With the way you coded it, when Geocoder.geocodePosition() is called, you are not guaranteed to have your latitude and longitude defined in state yet. So you may want to change how that's done. One possible fix for this is to place it in the callback of setState() like so:
navigator.geolocation.getCurrentPosition(
position => {
this.setState(
{
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
},
() => {
Geocoder.geocodePosition({
lat: this.state.latitude,
lng: this.state.longitude,
}).then(res => {
this.setState({
place: res[0].formattedAddress,
});
});
}
);
},
error => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);