react-native-background-geolocation
react-native-background-geolocation copied to clipboard
Don`t have background tracking (dot trail) with location setting 4 (While using the app) (Android)
Your Environment
- Plugin version: "^4.6.0"
- Platform: Android
- OS version: Android version 11
- Device manufacturer / model: Samsung / A12
- React Native version (
react-native -v): "^0.66.3" - Plugin config
What settings should we use in order to have background tracking (dot trail) with location setting 4 (While using the app)? How can we have pins on the map the whole path from begining to end without gaps? We wan’t to have pins while the app is minimised without activating the location setting on 5 (Always). Technically the app is activated and then minimised (still working on background) not killed.
// Starting the plugin
import BackgroundGeolocation from 'react-native-background-geolocation';
import LocationPermissions from '../../components/LocationPermissions/LocationPermissions';
import saveLocationService from '../../screens/Activity/Activity/components/MapComponent/services/SaveLocationService';
import {setBackgroundLocationPermission} from '../../store/backgroundGeolocation';
const sendLocation = saveLocationService.saveLocation();
let lastLatitude = null;
let lastLongtitude = null;
let isFirstLoad = true;
let shouldSkipProvider = true; // Provider change triggers when app is started and background location inits twice ( this is to prevent doubled initilization)
export const getCurrentUserLocation = () => {
return BackgroundGeolocation.getCurrentPosition({
timeout: 5,
persist: true,
maximumAge: 5000,
desiredAccuracy: 10,
samples: 1,
});
};
const backgroundLocation = (dispatch) => {
LocationPermissions.checkPermission().then(async ({permissionGranted}) => {
if (permissionGranted) {
BackgroundGeolocation.stop();
// Set if we have permission or not
setBackgroundLocationPermission(dispatch, true);
// Start the plugin
initBackgroundGeolocation();
} else {
// Set if we have permission or not
BackgroundGeolocation.stop();
BackgroundGeolocation.onProviderChange(onProviderChange);
setBackgroundLocationPermission(dispatch, false);
}
});
const initBackgroundGeolocation = () => {
BackgroundGeolocation.onProviderChange(onProviderChange);
BackgroundGeolocation.onLocation(onLocation, onLocationError);
BackgroundGeolocation.onHeartbeat(onHeartbeat);
BackgroundGeolocation.ready({
locationAuthorizationRequest: 'WhenInUse',
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
distanceFilter: 500,
activityRecognitionInterval: 10000, // Android
fastestLocationUpdateInterval: 10000,
forceReloadOnGeofence: true,
stopOnTerminate: true,
startOnBoot: true,
foregroundService: true,
heartbeatInterval: 60 * 10,
logLevel: BackgroundGeolocation.LOG_LEVEL_OFF,
autoSync: false,
reset: true,
extras: {
route_id: 1,
filterDistance: 500,
},
disableLocationAuthorizationAlert: true,
})
.then((state) => {
console.log('- BackgroundGeolocation is ready: ', state.enabled);
if (!state.enabled) {
BackgroundGeolocation.start(async () => {
if (isFirstLoad) {
const userLocation = await getCurrentUserLocation();
if (userLocation && !userLocation.sample) {
// Sending location coords to the server
sendLocation.fetch(userLocation);
}
isFirstLoad = false;
}
});
}
})
.catch((error) => {
console.log('- BackgroundGeolocation error: ', error);
});
};
const onLocation = (location) => {
if (location && !location.sample && location.extras.route_id === 1) {
if (
lastLongtitude &&
lastLongtitude.toFixed(7) !== location.coords.longitude.toFixed(7) &&
lastLatitude &&
lastLatitude.toFixed(7) !== location.coords.latitude.toFixed(7)
) {
// Sending location coords to the server
sendLocation.fetch(location);
lastLongtitude = location.coords.longitude;
lastLatitude = location.coords.latitude;
} else {
if (lastLongtitude === null && lastLatitude === null) {
lastLongtitude = location.coords.longitude;
lastLatitude = location.coords.latitude;
// Sending location coords to the server
sendLocation.fetch(location);
} else {
lastLongtitude = location.coords.longitude;
lastLatitude = location.coords.latitude;
}
}
}
};
const onHeartbeat = (location) => {
console.log('Heartbeat', location);
};
const onLocationError = (event) => {
console.warn('[event] location ERROR: ', event);
};
const onProviderChange = (provider) => {
if (shouldSkipProvider) {
shouldSkipProvider = false;
return;
}
switch (provider.status) {
case BackgroundGeolocation.AUTHORIZATION_STATUS_ALWAYS:
BackgroundGeolocation.setConfig({locationAuthorizationRequest: 'WhenInUse'}, () => {
// Set if we have permission or not
setBackgroundLocationPermission(dispatch, true);
// Start the plugin
initBackgroundGeolocation('WhenInUse');
});
break;
case BackgroundGeolocation.AUTHORIZATION_STATUS_WHEN_IN_USE:
BackgroundGeolocation.setConfig({locationAuthorizationRequest: 'WhenInUse'}, () => {
// Set if we have permission or not
setBackgroundLocationPermission(dispatch, true);
// Start the plugin
initBackgroundGeolocation('WhenInUse');
});
break;
case BackgroundGeolocation.AUTHORIZATION_STATUS_DENIED:
BackgroundGeolocation.stop();
BackgroundGeolocation.setConfig({locationAuthorizationRequest: 'Never'}, () => {
// Set if we have permission or not
setBackgroundLocationPermission(dispatch, false);
});
break;
default:
BackgroundGeolocation.stop();
// Set if we have permission or not
setBackgroundLocationPermission(dispatch, false);
break;
}
};
};
export default backgroundLocation;
Here is our New Workout Component: // Starting the workout.
import React, {useRef, useEffect} from 'react';
import {View, Pressable} from 'react-native';
import PropTypes from 'prop-types';
import {useTranslation} from 'react-i18next';
import MapView, {Marker, Polyline} from 'react-native-maps';
import BackgroundGeolocation from 'react-native-background-geolocation';
import {deviceWidth, deviceHeight} from 'utils';
import {Typography, GradientButton} from 'components';
import {SvgXml} from 'react-native-svg';
import {userPin, mapCenter} from '../../../Activity/components/MapIcons/MapIcons';
import {
convertToTimeString,
calculateDisplacementDifference,
} from '../../../Activity/components/MapComponent/helpers/helper';
import {useDispatch} from 'react-redux';
import {backgroundLocation} from '../../../../../utils';
import mapStyles from '../../../Activity/components/MapComponent/map.json';
import {getBackgroundGeolocationPermission} from '../../../../../store/backgroundGeolocation';
import {useSelector} from 'react-redux';
import HikingMapOverlay from '../HikingMapOverlay/HikingMapOverlay';
import styles from './hikingMapStyles';
const LATITUDE_DELTA = 0.00922;
const LONGITUDE_DELTA = LATITUDE_DELTA * (deviceWidth / deviceHeight);
const distanceFilterMetres = 10;
const HikingMap = ({state, setState}) => {
const {t} = useTranslation();
const mapView = useRef(null);
const dispatch = useDispatch();
const isPermmissionGranteed = useSelector((state) => getBackgroundGeolocationPermission(state));
const setCenter = async (location, isFirstTime) => {
if (location === null && isFirstTime) {
location = await BackgroundGeolocation.getCurrentPosition({
timeout: 30,
persist: true,
maximumAge: 5000,
desiredAccuracy: 10,
samples: 3,
});
addMarker(location);
}
if (!mapView) {
return;
}
if (location) {
mapView.current.animateToRegion({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
};
const centerMap = async () => {
location = await BackgroundGeolocation.getCurrentPosition({
timeout: 30,
persist: true,
maximumAge: 5000,
desiredAccuracy: 10,
samples: 1,
});
if (location) {
mapView.current.animateToRegion({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
};
const backgroundGeolocationSevices = async () => {
await BackgroundGeolocation.changePace(true);
BackgroundGeolocation.resetOdometer();
BackgroundGeolocation.setConfig({
locationAuthorizationRequest: 'WhenInUse',
distanceFilter: distanceFilterMetres,
extras: {
route_id: 2,
filterDistance: 10,
},
}).then(async (state) => {
setState((prevState) => ({
...prevState,
enabled: state.enabled,
isMoving: state.isMoving,
showsUserLocation: state.enabled,
hikeStartTimestamp: Date.now(),
}));
BackgroundGeolocation.onLocation(onLocation, onLocationError);
});
};
const onLocation = (location) => {
if (location.error === 0) {
BackgroundGeolocation.stop();
return;
}
if (!location.sample && location.extras.route_id === 2) {
addMarker(location);
setState((prevState) => ({
...prevState,
lastLocation: location,
}));
setCenter(location);
}
};
// Handle Location Data
useEffect(() => {
if (state.lastLocation !== null) {
let displacementDifference = 0;
const altitudeValue = state.lastLocation.coords.altitude;
if (state.altitude !== null) {
displacementDifference = parseInt(
state.displacementDiff +
calculateDisplacementDifference(
parseFloat(state.displacement),
state.lastLocation.coords.altitude,
),
);
}
setState((prevState) => ({
...prevState,
distance: parseInt(state.lastLocation.odometer).toFixed(0),
altitude: altitudeValue,
displacement: state.altitude === null ? state.lastLocation.coords.altitude : state.altitude,
displacementDiff: displacementDifference,
}));
}
}, [state.lastLocation]);
const onLocationError = (event) => {
console.warn('[event] location ERROR: ', event);
};
const addMarker = (location) => {
const marker = {
key: location.uuid,
title: location.timestamp,
heading: location.coords.heading,
coordinate: {
latitude: location.coords.latitude,
longitude: location.coords.longitude,
},
};
setState((prevState) => ({
...prevState,
markers: [...prevState.markers, marker],
coordinates: [
...prevState.coordinates,
{
latitude: location.coords.latitude,
longitude: location.coords.longitude,
},
],
}));
};
useEffect(() => {
setCenter(null, true);
backgroundGeolocationSevices();
return () => {
BackgroundGeolocation.stop();
backgroundLocation(dispatch, false);
};
}, []);
useEffect(() => {
if (state.hikeStartTimestamp) {
var workoutTimer = setInterval(() => {
const timeString = convertToTimeString(state.hikeStartTimestamp);
setState((prevState) => ({...prevState, timeString: timeString}));
}, 1000);
}
return () => clearInterval(workoutTimer);
}, [state.hikeStartTimestamp]);
return (
<>
<View style={styles.finishHiking}>
<GradientButton
text={t('activity.end')}
size="small"
onPress={() => setState((prevState) => ({...prevState, isFinishWorkoutModalVisible: true}))}
/>
</View>
{!isPermmissionGranteed && <HikingMapOverlay />}
<MapView
customMapStyle={mapStyles}
ref={mapView}
style={styles.map}
showsUserLocation={true}
scrollEnabled={true}
showsMyLocationButton={false}
showsPointsOfInterest={false}
showsScale={false}
showsTraffic={false}
toolbarEnabled={false}>
<Polyline
key="polyline"
coordinates={state.coordinates}
geodesic={true}
strokeColor="transparent"
strokeWidth={0}
zIndex={0}
/>
{state.markers?.map((marker, index) => (
<Marker
key={`markerKey${index}`}
coordinate={marker.coordinate}
anchor={{x: 0, y: 0.1}}
title={marker.title}>
{index === 0 ? (
<View style={{width: 36.4, height: 49}}>
<SvgXml xml={userPin} width="36" height="49" />
</View>
) : (
<View style={styles.markerIcon}></View>
)}
</Marker>
))}
</MapView>
<View style={styles.centerButton}>
<Pressable onPress={() => centerMap()}>
<SvgXml xml={mapCenter} width="25" height="25" />
</Pressable>
</View>
<View style={styles.workoutInfo}>
<View style={styles.leftView}>
<Typography name="normal" text={t('activity.distance')} style={styles.text} />
<Typography
name="extraLargeNormal"
text={`${state.distance} ${t('activity.metersShort')}`}
style={styles.text}
/>
</View>
<View>
<View style={styles.rightView}>
<Typography name="tiny" text={t('activity.time')} style={styles.text} />
<Typography name="large" text={state.timeString} style={styles.text} />
</View>
<View style={styles.rightView}>
<Typography name="tiny" text={t('activity.displacement')} style={styles.text} />
<Typography
name="large"
text={`${state.displacementDiff.toFixed(0)} ${t('activity.metersShort')}`}
style={styles.text}
/>
</View>
</View>
</View>
</>
);
};
HikingMap.propTypes = {
state: PropTypes.object.isRequired,
setState: PropTypes.func.isRequired,
};
export default HikingMap;
Expected Behavior
We have pins on the map the whole path from begining to end without gaps.
Actual Behavior
The app is not drawing on background and we have gaps.
Steps to Reproduce
The scenario is the following:
- We start the app
- We initiate “tracking mode”
- We start walking while the app is on the screen and the app is placing pins on the map
- We minimise the app, lock the phone and put it in our pocket.
- We walk for another 3 km and then we take the phone out of the pocket, unlock the screen, navigate between all open apps and chose the tracking app to see the map and pins.
- The result is that there are no pins from the time when we minimised the app and locked the phone and the time we unlocked the phone and viewed on screen the tracking app.
- After that we continue the walk with the app on screen for another 3 km and again we have pins on the map and everything works perfectly.
In the end we have tracking for in the begining then gap (no tracking) in the middle and again tracking in the end.
Context
New Workout functionality
Debug logs
Logs
PASTE_YOUR_LOGS_HERE
Why have you posted two issues (#1475
-
OS version: ??
- See Wiki Debugging. Learn how to observe the plugin's logs. See API docs
Config.logLevel,Config.debug - See https://dontkillmyapp.com
Why do you call .start() immediately after .ready() is called?
if (!state.enabled) {
BackgroundGeolocation.start(async () => {
if (isFirstLoad) {
const userLocation = await getCurrentUserLocation();
if (userLocation && !userLocation.sample) {
// Sending location coords to the server
sendLocation.fetch(userLocation);
}
isFirstLoad = false;
}
});
}
-
Call
.ready()as soon as your app boots, regardless of whether you don't want to start tracking. Calling.ready()does not imply "start tracking"..ready()is a signal to the plugin that your app has launched. You do not call.ready()only when you're ready to start location tracking. -
When you wish to start a "New Workout", then you do this:
await BackgroundGeolocation.start();
await BackgroundGeolocation.changePace();
- When you wish to stop a "Workout", you do this:
BackgroundGeolocation.stop()
Also, did you follow this instruction in the Setup Guide?
.ready() is designed to be called only once, at the boot of your app.
Your logs are full of:
⚠️ #ready already called. Redirecting to #setConfig
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. You may also mark this issue as a "discussion" and I will leave this open.